需求:
A、依次讀取 A|B|C 三個檔案,如果有失敗,則立即終止。
B、同時讀取 A|B|C 三個檔案,如果有失敗,則立即終止。
一、callback
需求A:
let read = function (code) { if (code) { return true; } else { return false; }}let readFileA = function (callback) { if (read(1)) { return callback(null, "111"); } else { return callback("a fail"); }}let readFileB = function (callback) { if (read(1)) { return callback(null, "222"); } else { return callback("b fail"); }}let readFileC = function (callback) { if (read(1)) { return callback(null, "333"); } else { return callback("c fail"); }}readFileA(function (err, data) { if (err) { console.log("open file " + err); return; } console.log("讀取 a.txt 成功!內容:" + data); readFileB(function (err, data) { if (err) { console.log("open file " + err); return; } console.log("讀取 b.txt 成功!內容:" + data); readFileC(function (err, data) { if (err) { console.log("open file " + err); return; } console.log("讀取 c.txt 成功!內容:" + data); }); });});
return:
讀取 a.txt 成功!內容:111讀取 b.txt 成功!內容:222讀取 c.txt 成功!內容:333
需求B:太噁心了,不寫了,總之很繁瑣.
二、async.js
async.js 庫的詳細介紹可以見:[待寫]
需求A:
async.series
var async = require("async");let read = function (code) { if (code) { return true; } else { return false; }}let readFileA = function (callback) { if (read(1)) { return callback(null, "111"); } else { return callback("a fail"); }}let readFileB = function (callback) { if (read(0)) { return callback(null, "222"); } else { return callback("b fail"); }}let readFileC = function (callback) { if (read(1)) { return callback(null, "333"); } else { return callback("c fail"); }}async.series([readFileA, readFileB, readFileC], function (err, datas) { if (err) { console.log("open file " + err); } console.log(datas); return; });
當第二個 readFileB() 讀取失敗的話:
return:
open file b fail[ '111', undefined ]
需求B:
async.parallel
var async = require("async");let read = function (code) { if (code) { return true; } else { return false; }}let readFileA = function (callback) { if (read(1)) { return callback(null, "111"); } else { return callback("a fail"); }}let readFileB = function (callback) { setTimeout(() => { if (read(0)) { return callback(null, "222"); } else { return callback("b fail"); } }, 1000);}let readFileC = function (callback) { if (read(1)) { return callback(null, "333"); } else { return callback("c fail"); }}async.parallel([readFileA, readFileB, readFileC], function (err, datas) { if (err) { console.log("open file " + err); } console.log(datas); return; });
當第二個 readFileB() 讀取失敗 (注意我給它加了 setTimeout,為了體現跟上面串列結果的不一樣) 的話:
return:
open file b fail[ '111', undefined, '333' ]
總結:async.js 跟 callback 比的好處:
1、代碼量少了,解決了回調地獄金字塔的缺陷
2、async 的第二個參數回呼函數裡,可以統一處理錯誤(建議用不同的 Error 類作區分)
3、成功返回的結果 datas 可以匯總到一個數組中方便處理
三、promise
[拓展]
promise 知識
new Promise()
// promise 在 new 的時候已經開始運行 new Promise(() => console.log("I have already started!"));
return:
I have already started!
promise.then(successCallback, failureCallback);
new Promise((resolve, reject) => resolve()).then(function (data) { console.log("success");}, function (data) { console.log("fail");})
return:
success
promise.catch(failureCallback)
// promise.catch(failureCallback) 是 promise.then(null, failureCallback) 的縮減形式new Promise((resolve, reject) => reject()).catch( function (data) { console.log("fail");})
return:
fail
鏈式調用
// 鏈式調用的原理:then 函數會返回一個新的 promisenew Promise((resolve, reject) => reject()).then(function (data) { console.log("success_1");}, function (err) { console.log("fail_1");}).then(function (data) { console.log("success_2");}, function (err) { console.log("fail_2");});
return:
fail_1success_2
提問
問1:then 函數會返回一個新的 promise,但是 then 的 successCallback 和 failureCallback 這兩個回呼函數裡都沒法調用 resolve() 和 reject(),那這個新的 promise 如何指定最終狀態呢?
| then 的 successCallback 和 failureCallback 裡 |
等同於 |
| 不返回 |
resolve(undefined) |
| return 1 |
resolve(1) |
| return Promise.resolve() |
resolve() |
| return Promise.reject() |
reject() |
| throw Error() |
reject() |
| return new Promise() |
以此類推 |
而普通的 promise 對象,如果不顯示調用 resolve/reject ,則沒有任何反應,例如:
new Promise((resolve, reject) => {return 1;}).then(function (data) { console.log("success");}, function (err) { console.log("fail");});
return:
沒有任何輸出
問2:then 函數如果 successCallback 和 failureCallback 都為 null,會發生什嗎?
什麼都不會發生,.then(null, null) 只要一方為 null,等於交給下一個 then 去接管這個回調
new Promise((resolve, reject) => reject()) .then(null, null) .then(null, null) .then(null, null) .then(null, null) .then(function (data) { console.log("success_2"); }, function (err) { console.log("fail_2"); });
所以按照上面 2 個提問揭示的規律,我們可以寫成下面優雅的代碼:
// 鏈式調用的原理:then 函數會返回一個新的 promisenew Promise((resolve, reject) => resolve()).then((data) => { console.log("success_1");}).then((data) => { console.log("success_2"); throw Error("error");}).then((data) => { console.log("success_3");}).catch((err) => { console.log(err);});
return:
success_1success_2Error: error ……
註:.catch() 後還可以繼續接 .then() 或 .catch()
這就達到了如下別人家同步代碼的清晰的表達:
try { let result = syncDoSomething(); let newResult = syncDoSomethingElse(result); let finalResult = syncDoThirdThing(newResult); console.log(`Got the final result: ${finalResult}`); } catch(error) { console.log(error);}
所以,需求A:
let read = function (code) { if (code) { return true; } else { return false; }}let readFileA = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("111"); } else { reject("a fail"); } });}let readFileB = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("222"); } else { reject("b fail"); } });}let readFileC = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("333"); } else { reject("c fail"); } });}//[串列] 情境:依次預先載入多個資源,如果中途有失敗,則進入 .catch()readFileA().then(function (data) { console.log("讀取 a.txt 成功!內容:" + data); return readFileB();}).then(function (data) { console.log("讀取 b.txt 成功!內容:" + data); return readFileC();}).then(function (data) { console.log("讀取 c.txt 成功!內容:" + data); return "讀取結束";}).then(function (data) { console.log(data); return;}).catch(function (err) { console.log("open file " + err);})
promise vs 事件監聽
a. 事件監聽更多的是針對同一對象上發生多次的事情(如 keyup、touchstart 等)
而 promise 更多的表現這個對象最終走向什麼狀態,且不可改變。
但有個神奇的特型是一致的,事件監聽和 promise 都可以對同一事件的反應綁定多次的回呼函數,如下面例子所示:
let promise = new Promise((resolve, reject) => { console.log("I have already started!"); resolve();})setTimeout(() => { promise.then(function (data) { console.log("success_1"); throw new Error(); }, function (err) { console.log("fail_1"); }); }, 2000);setTimeout(() => { promise.then(function (data) { console.log("success_2"); }, function (err) { console.log("fail_2"); }); }, 4000);
return:
I have already started!//又等待了2秒success_1(node:13150) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error(node:13150) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.//又等待了2秒success_2
b. 跟 事件監聽不一樣,如果 promise 已成功或失敗了,過段時間再添加了回呼函數,則還是可以成功調用回調。這個上面的例子也可以體現。
c. 事件監聽更多的關注某些功能的準確時間,promise 更多地是關注對結果作出的反應。
promise 擴充 API
Promise.resolve() 和
Promise.reject()
手動建立一個已經 resolve 或者 reject 的 promise 的快捷方法。
promise.all:可以實現
需求B:
//promise.all [並行] 情境:預先載入多個資源,都完成後才能進入頁面Promise.all([readFileA(), readFileB(), readFileC()]).then(function (datas) { console.log(datas); //所有promise都resolve,返回array return;}).catch(function (err) { console.log("open file " + err); //只要有一個promise是reject,返回這個reject的value})
promise.race:
//promise.race [並行] 情境:taskA:fetch圖片,taskB:settimeout拋錯,讓兩個task賽跑實現請求逾時報錯功能Promise.race([taskA(), taskB()]).then(function (data) { //進到resolve還是reject回調只取決於第一個確定狀態的Promise console.log(data); return;}).catch(function (err) { console.log("讀取圖片逾時");})
總結:promise 跟 callback 比的好處:
1、代碼量少了,解決了回調地獄金字塔的缺陷
2、.catch 可以統一處理錯誤(建議用不同的 Error 類作區分)
四、async / await
需求A:
let read = function (code) { if (code) { return true; } else { return false; }}let readFileA = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("111"); } else { reject("a fail"); } });}let readFileB = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("222"); } else { reject("b fail"); } });}let readFileC = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("333"); } else { reject("c fail"); } });}async function test() { try { let re_a = await readFileA(); let re_b = await readFileB(); let re_c = await readFileC(); console.log({re_a, re_b, re_c}); //如果都成功,return: { re_a: '111', re_b: '222', re_c: '333' } } catch (err) { console.log(err); // 如果b失敗,return: b fail }}test();
總結:async / await 跟 callback 比的好處:
1、代碼量最少,解決了回調地獄金字塔的缺陷(Promise 通過 then 鏈來解決 callback 多層回調金字塔的問題,現在又用 async/await 來進一步最佳化它)(基於 promise 的 async / await 也試圖淘汰 promise)
2、.catch 可以統一處理錯誤(建議用不同的 Error 類作區分)
[拓展]
1、async 函數就是 Generator 函數的文法糖,本質上並不是同步代碼
2、async 用於申明一個 function 是非同步,而 await (async wait) 用於等待一個非同步方法呼叫執行完成。
3、await 只能出現在 async 函數中,所以在代碼的頂層,我們無法使用 await,所以添加它 .then/catch 來處理最終結果或掉落錯誤是正常的做法。
try { let re_a = await readFileA(); let re_b = await readFileB(); let re_c = await readFileC(); console.log({re_a, re_b, re_c}); } catch (err) { console.log(err); }
return:
報錯
或者頂層使用立即執行函數運算式(IIFE)
(async () => { try { let re_a = await readFileA(); let re_b = await readFileB(); let re_c = await readFileC(); console.log({re_a, re_b, re_c}); } catch (err) { console.log(err); } })()
return:
{ re_a: '111', re_b: '222', re_c: '333' }
上面的例子還可以這樣寫:
async function test() { try { let re_a = await readFileA(); let re_b = await readFileB(); let re_c = await readFileC(); console.log({re_a, re_b, re_c}); //如果都成功,return: { re_a: '111', re_b: '222', re_c: '333' } } catch (err) { console.log(err); // 如果b失敗,return: b fail }}test().then(function(data){ console.log("success");},function(err){ console.log("fail"); });
return:
{ re_a: '111', re_b: '222', re_c: '333' }success
4、見上例,實際上 async 申明的 function 返回的就是一個 Promise 對象,這就是 await 必須用在 async 函數中的原因。async 函數調用不會造成阻塞,它內部所有的阻塞都被封裝在一個 Promise 對象中非同步執行。
區別是,async 申明的 function 裡可以通過 return值 / 拋異常 來實現普通 Promise 的 resolve() / reject()
下面是對等關係:
// async 函數async function foo () { return 'a'}// Promisefunction foo () { return Promise.resolve('a')}
// async 函數async function foo () { throw new Error('error')}// Promisefunction foo () { return Promise.reject(new Error('error'))}
用
promise.all 實現
需求B
async/await 同樣適用於 Promise.all,因為 Promise.all 本身返回的就是 promise 對象。
let read = function (code) { if (code) { return true; } else { return false; }}let readFileA = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("111"); } else { reject("a fail"); } });}let readFileB = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("222"); } else { reject("b fail"); } });}let readFileC = function () { return new Promise(function (resolve, reject) { if (read(1)) { resolve("333"); } else { reject("c fail"); } });}async function test() { try { let re_a = await readFileA(); let re_b = await readFileB(); let re_c = await readFileC(); console.log({re_a, re_b, re_c}); } catch (err) { console.log(err); }}async function test() { try { let results = await Promise.all([ readFileA(), readFileB(), readFileC(), ]); console.log(results); } catch (err) { console.log(err); }}test();
#### 參考資料
[使用 promises]
developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises
[理解 JavaScript 的 async/await]
1190000007535316
[javascript.info-Async/await]