callback vs async.js vs promise vs async / await

需求:javascript

A、依次讀取 A|B|C 三個文件,若是有失敗,則當即終止。
B、同時讀取 A|B|C 三個文件,若是有失敗,則當即終止。java

1、callback


需求Anode

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:segmentfault

讀取 a.txt 成功!內容:111
讀取 b.txt 成功!內容:222
讀取 c.txt 成功!內容:333

需求B:太噁心了,不寫了,總之很繁瑣.數組

2、async.js


async.js 庫的詳細介紹能夠見:[待寫]promise

需求A異步

async.seriesasync

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() 讀取失敗的話:ide

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 比的好處:

一、代碼量少了,解決了回調地獄金字塔的缺陷

二、async 的第二個參數回調函數裏,能夠統一處理錯誤(建議用不一樣的 Error 類做區分)

三、成功返回的結果 datas 能夠彙總到一個數組中方便處理

3、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 函數會返回一個新的 promise
new 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_1
success_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 函數若是 successCallbackfailureCallback 都爲 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 函數會返回一個新的 promise
new 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_1
success_2
Error: 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 比的好處:

一、代碼量少了,解決了回調地獄金字塔的缺陷

二、.catch 能夠統一處理錯誤(建議用不一樣的 Error 類做區分)

4、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 比的好處:

一、代碼量最少,解決了回調地獄金字塔的缺陷(Promise 經過 then 鏈來解決 callback 多層回調金字塔的問題,如今又用 async/await 來進一步優化它)(基於 promise 的 async / await 也試圖淘汰 promise)

二、.catch 能夠統一處理錯誤(建議用不一樣的 Error 類做區分)


[拓展]

一、async 函數就是 Generator 函數的語法糖,本質上並非同步代碼

二、async 用於申明一個 function 是異步的,而 await (async wait) 用於等待一個異步方法執行完成。

三、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

四、見上例,實際上 async 申明的 function 返回的就是一個 Promise 對象,這就是 await 必須用在 async 函數中的緣由。async 函數調用不會形成阻塞,它內部全部的阻塞都被封裝在一個 Promise 對象中異步執行。

區別是,async 申明的 function 裏能夠經過 return值 / 拋異常 來實現普通 Promise 的 resolve() / reject()

下面是對等關係:

// async 函數
async function foo () {
  return 'a'
}
// Promise
function foo () {
  return Promise.resolve('a')
}
// async 函數
async function foo () {
  throw new Error('error')
}
// Promise
function 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]
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises

[理解 JavaScript 的 async/await]
http://www.javashuo.com/article/p-spqrnyuu-km.html

[javascript.info-Async/await]
https://javascript.info/async-await#async-functions

相關文章
相關標籤/搜索