非同步
非同步
這種不連續的執行,就叫做非同步。相應地,連續的執行,就叫做同步。
高階函式
函式作為一等公民,可以作為引數和返回值
可以用於批量生成函式
let toString = Object.prototype.toString; let isString = function (obj) { return toString.call(obj) == `[object String]`; } let isFunction = function (obj) { return toString.call(obj) == `[object Function]`; } let isType = function (type) { return function (obj) { return toString.call(obj) == `[object ${type}]`; } }
可以用於需要呼叫多次才執行的函式
let after = function(times,task){ return function(){ if(times--==1){ return task.apply(this,arguments); } } } let fn = after(3,function(){ console.log(3);}); fn();
非同步程式設計的語法目標,就是怎樣讓它更像同步程式設計,有以下幾種
- 回撥函式實現
- 事件監聽
- 釋出訂閱
- Promise/A+ 和生成器函式
- async/await
回撥
所謂回撥函式,就是把任務的第二段單獨寫在一個函式裡面,等到重新執行這個任務的時候,就直接呼叫這個函式
fs.readFile('某個檔案', function (err, data) { if (err) throw err; console.log(data); });
這是一個錯誤優先的回撥函式(error-first callbacks)
,這也是Node.js
本身的特點之一。
回撥的問題
異常處理
try{ //xxx }catch(e){//TODO} 非同步程式碼時try catch不再生效 let async = function(callback){ try{ setTimeout(function(){ callback(); },1000) }catch(e){ console.log('捕獲錯誤',e); } } async(function(){ console.log(t); });
因為這個回撥函式被存放了起來,直到下一個事件環的時候才會取出,try只能捕獲當前迴圈內的異常,對callback非同步無能為力。
Node
在處理異常有一個約定,將異常作為回撥的第一個實參傳回,如果為空表示沒有出錯。
async(function(err,callback){ if(err){ console.log(err); } });
非同步方法也要遵循兩個原則
- 必須在非同步之後呼叫傳入的回撥函式
- 如果出錯了要向回撥函式傳入異常供呼叫者判斷
let async = function(callback){ try{ setTimeout(function(){ if(success) callback(null); else callback('錯誤'); },1000) }catch(e){ console.log('捕獲錯誤',e); } }
回撥地獄
非同步多級依賴的情況下巢狀非常深,程式碼難以閱讀的維護
let fs = require('fs'); fs.readFile('template.txt','utf8',function(err,template){ fs.readFile('data.txt','utf8',function(err,data){ console.log(template+' '+data); }) })
非同步流程解決方案
事件釋出/訂閱模型
訂閱事件實現了一個事件與多個回撥函式的關聯
let fs = require('fs'); let EventEmitter = require('events'); let eve = new EventEmitter(); let html = {}; eve.on('ready',function(key,value){ html[key] = value; if(Object.keys(html).length==2){ console.log(html); } }); function render(){ fs.readFile('template.txt','utf8',function(err,template){ eve.emit('ready','template',template); }) fs.readFile('data.txt','utf8',function(err,data){ eve.emit('ready','data',data); }) } render();
哨兵變數
let fs = require('fs'); let after = function(times,callback){ let result = {}; return function(key,value){ result[key] = value; if(Object.keys(result).length==times){ callback(result); } } } let done = after(2,function(result){ console.log(result); }); function render(){ fs.readFile('template.txt','utf8',function(err,template){ done('template',template); }) fs.readFile('data.txt','utf8',function(err,data){ done('data',data); }) } rende
Promise/Deferred模式
生成器Generators/ yield
當你在執行一個函式的時候,你可以在某個點暫停函式的執行,並且做一些其他工作,然後再返回這個函式繼續執行, 甚至是攜帶一些新的值,然後繼續執行。
上面描述的場景正是JavaScript生成器函式所致力於解決的問題。當我們呼叫一個生成器函式的時候,它並不會立即執行, 而是需要我們手動的去執行迭代操作(next方法)。也就是說,你呼叫生成器函式,它會返回給你一個迭代器。迭代器會遍歷每個中斷點。
next
方法返回值的value
屬性,是Generator
函式向外輸出資料next
方法還可以接受引數,這是向 Generator 函式體內輸入資料
生成器的使用
function* foo () { var index = 0; while (index < 2) { yield index++; //暫停函式執行,並執行yield後的操作 } } var bar =foo(); // 返回的其實是一個迭代器 console.log(bar.next());// { value: 0, done: false } console.log(bar.next());// { value: 1, done: false } console.log(bar.next());// { value: undefined, done: true }
co是一個為Node.js和瀏覽器打造的基於生成器的流程控制工具,藉助於Promise,你可以使用更加優雅的方式編寫非阻塞程式碼。
let fs = require('fs'); function readFile(filename) { return new Promise(function (resolve, reject) { fs.readFile(filename, function (err, data) { if (err) reject(err); else resolve(data); }) }) } function *read() { let template = yield readFile('./template.txt'); let data = yield readFile('./data.txt'); return template + '+' + data; } co(read).then(function (data) { console.log(data); }, function (err) { console.log(err); }); function co(gen) { let it = gen(); return new Promise(function (resolve, reject) { !function next(lastVal) { let {value, done} = it.next(lastVal); if (done) { resolve(value); } else { value.then(next, reason => reject(reason)); } }(); }); }
Async/ await
Async
的優點
- 內建執行器
- 更好的語義
- 更廣的適用性
let fs = require('fs'); function readFile(filename) { return new Promise(function (resolve, reject) { fs.readFile(filename, 'utf8', function (err, data) { if (err) reject(err); else resolve(data); }) }) } async function read() { let template = await readFile('./template.txt'); let data = await readFile('./data.txt'); return template + '+' + data; } let result = read(); result.then(data=>console.log(data));
async 函式的實現
async
函式的實現,就是將Generator
函式和自動執行器,包裝在一個函式裡。
async function read() { let template = await readFile('./template.txt'); let data = await readFile('./data.txt'); return template + '+' + data; }
// 等同於 function read(){ return co(function*() { let template = yield readFile('./template.txt'); let data = yield readFile('./data.txt'); return template + '+' + data; }); } async_function- generator