node.js非同步控制流程程 回調,事件,promise和async/await

來源:互聯網
上載者:User

標籤:自己   並且   推薦   ror   blog   解決方案   方案   失敗   完全   

寫這個問題是因為最近看到一些初學者用回調用的不亦樂乎,最後代碼左調來又調去很不直觀。

首先上結論:推薦使用async/await或者co/yield,其次是promise,再次是事件,回調不要使用。

接下來是解析,為什麼我會有這樣的結論

 

首先是回調,理解上最簡單,就是我把任務分配出去,當你執行完了我就能從你那裡拿到結果執行相應的回調,

這裡示範一個對setTimeout的封裝,規定時間後列印相應結果並執行回呼函數

並且這個函數傳給回呼函數的參數符合node標準,第一個為error資訊,如果出錯error不為null,正常執行則為null

var i = 0;function sleep(ms, callback) {    setTimeout(function () {        console.log(‘我執行完啦!‘);        i++;        if (i >= 2) callback(new Error(‘i大於2‘), null);        else callback(null, i);    }, ms);}sleep(3000, function (err,val) {    if(err) console.log(‘出錯啦:‘+err.message);    else console.log(val);})//執行結果:3s後列印 "我執行完啦","1"

 

 

這樣的代碼看上去並不會很不舒服,而且也比較好理解,但是假如我要暫停多次呢

調用的代碼就變成了如下:

sleep(1000, function (err, val) {    if (err) return console.log(err.message);;    console.log(val);    sleep(1000, function (err, val) {        if (err) return console.log(err.message);        console.log(val);        sleep(1000, function (err, val) {            if (err) console.log(err.message);            else console.log(val);        })    })})

可以看得出來,嵌套得很深,你可以把這三次操作看成三個非同步任務,並且還有可能繼續嵌套下去,這樣的寫法顯然是反人類的。

嵌套得深首先一個不美觀看的很不舒服,第二個如果回呼函數出錯了也難以判斷在哪裡出錯的。

 

於是改進方法就是事件監聽,每次調用一個非同步函數都返回一個EventEmitter對象,並在執行成功時調用done事件,

失敗時調用error事件

var i = 0;function sleep(ms) {    var emitter = new require(‘events‘)();    setTimeout(function () {        console.log(‘我執行完啦!‘);        i++;        if (i >= 2) emitter.emit(‘error‘, new Error(‘i大於2‘));        else emitter.emit(‘done‘, i);    }, ms);}var emit = sleep(3000);emit.on(‘done‘,function (val) {    console.log(‘成功:‘ + val);})emit.on(‘error‘,function(err){    console.log(‘出錯了:‘ + err.message);})

這樣寫比之前的好處在於能添加多個回呼函數,每個回呼函數都能獲得值並進行相應操作。但這並沒有解決回調嵌套的問題,

比如這個函數多次調用還是必須寫在ondone的回呼函數裡,看起來還是很不方便。

所以比較普遍的解決方案是Promise。

promise和事件類別似,你可以把它看成只觸發兩個事件的event對象,但是事件具有即時性,觸發之後這個狀態就不存在了,這個

事件已經觸發過了,你就再也拿不到值了,而promise不同,promise只有兩個狀態resolve和reject,當它觸發任何一個狀態後

它會將當前的值緩衝起來,並在有回呼函數添加進來的時候嘗試調用回呼函數,如果這個時候還沒有觸發resolve或者reject,那麼

回呼函數會被緩衝,等待調用,如果已經有了狀態(resolve或者reject),則立刻調用回呼函數。並且所有回呼函數在執行後都立即

被銷毀。

代碼如下:

var i = 0;//函數返回promisefunction sleep(ms) {    return new Promise(function (resolve, reject) {        setTimeout(function () {            console.log(‘我執行好了‘);            i++;            if (i >= 2) reject(new Error(‘i>=2‘));            else resolve(i);        }, ms);    })}sleep(1000).then(function (val) {    console.log(val);    return sleep(1000)}).then(function (val) {    console.log(val);    return sleep(1000)}).then(function (val) {    console.log(val);    return sleep(1000)}).catch(function (err) {    console.log(‘出錯啦:‘ + err.message);})

這個例子中,首先它將原本嵌套的回呼函數展開了,現在看的更舒服了,並且由於promise的冒泡性質,當promise鏈中的任意一個

函數出錯都會直接拋出到鏈的最底部,所以我們統一用了一個catch去捕獲,每次promise的回調返回一個promise,這個promise

把下一個then當作自己的回呼函數,並在resolve之後執行,或在reject後被catch出來。這種鏈式的寫法讓函數的流程比較清楚了,

拋棄了嵌套,終於能平整的寫代碼了。

但promise只是解決了回調嵌套的問題,並沒有解決回調本身,我們看到的代碼依然是用回調阻止的。於是這裡就引入了async/await

關鍵字。

 

async/await是es7的新標準,並且在node7.0中已經得到支援,只是需要使用harmony模式去運行。

async函數定義如下

async function fn(){    return 0;}

即使用async關鍵字修飾function即可,async函數的特徵在於調用return返回的並不是一個普通的值,而是一個Promise對象,如果

正常return了,則返回Promise.resolve(傳回值),如果throw一個異常了,則返回Promise.reject(異常)。也就是說async函數的返回

值一定是一個promise,只是你寫出來是一個普通的值,這僅僅是一個文法糖。

await關鍵字只能在async函數中才能使用,也就是說你不能在任意地方使用await。await關鍵字後跟一個promise對象,函數執行到await後會退出該函數,直到事件輪詢檢查到Promise有了狀態resolve或reject 才重新執行這個函數後面的內容。

首先我用剛剛的例子展示async/await的神奇之處

var i = 0;//函數返回promisefunction sleep(ms) {    return new Promise(function (resolve, reject) {        setTimeout(function () {            console.log(‘我執行好了‘);            i++;            if (i >= 2) reject(new Error(‘i>=2‘));            else resolve(i);        }, ms);    })}(async function () {    try {        var val;        val = await sleep(1000);        console.log(val);        val = await sleep(1000);        console.log(val);        val = await sleep(1000);        console.log(val);    }    catch (err) {        console.log(‘出錯啦:‘+err.message);    }} ())

 

看上去代碼是完全同步的,每等待1s後輸出一次,並且在sleep返回的promise中狀態為reject的時候還能被try...catch出來。

那麼這到底是怎麼回事呢 我們來看一張圖

這段代碼和剛剛的代碼一樣,只是在async函數被調用後輸出了一次"主程式沒有被調用",結果如下

我們發現後面輸出的話是先列印的,這好像和我們的代碼順不一樣,這是怎麼回事呢。

 

 

總的來說async/await是promise的文法糖,但它能將原本非同步代碼寫成同步的形式,try...catch也是比較友好的捕獲異常的方式

所以在今後寫node的時候盡量多用promise或者async/await,對於回調就不要使用了,大量嵌套真的很反人類。

node.js非同步控制流程程 回調,事件,promise和async/await

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.