如何解決異步回調地獄


promise、generator、async/await

什么是async?

歡迎留言討論

async 函數是 Generator 函數的語法糖。使用 關鍵字 async 來表示,在函數內部使用 await 來表示異步。相較於 Generatorasync 函數的改進在於下面四點:

  • 內置執行器Generator 函數的執行必須依靠執行器,而 async 函數自帶執行器,調用方式跟普通函數的調用一樣

  • 更好的語義asyncawait 相較於 *yield 更加語義化

  • 更廣的適用性co 模塊約定,yield 命令后面只能是 Thunk 函數或 Promise對象。而 async 函數的 await 命令后面則可以是 Promise 或者 原始類型的值(Number,string,boolean,但這時等同於同步操作)

  • 返回值是 Promiseasync 函數返回值是 Promise 對象,比 Generator 函數返回的 Iterator 對象方便,可以直接使用 then() 方法進行調用

此處總結參考自:理解async/await

async是ES7新出的特性,表明當前函數是異步函數,不會阻塞線程導致后續代碼停止運行。

怎么用

申明之后就可以進行調用了

async function asyncFn() {   return 'hello world'; } asyncFn();復制代碼

這樣就表示這是異步函數,返回的結果

     

返回的是一個promise對象,狀態為resolved,參數是return的值。那再看下面這個函數

async function asyncFn() { return '我后執行' } asyncFn().then(result => { console.log(result); }) console.log('我先執行');復制代碼

 

上面的執行結果是先打印出'我先執行',雖然是上面asyncFn()先執行,但是已經被定義異步函數了,不會影響后續函數的執行。

 

現在理解了async基本的使用,那還有什么特性呢?

async定義的函數內部會默認返回一個promise對象,如果函數內部拋出異常或者是返回reject,都會使函數的promise狀態為失敗reject

 

 

 

async function e() { throw new Error('has Error'); } e().then(success => console.log('成功', success)) .catch(error => console.log('失敗', error));復制代碼

 

我們看到函數內部拋出了一個異常,返回rejectasync函數接收到之后,判定執行失敗進入catch,該返回的錯誤打印了出來。

 

 

 

async function throwStatus() { return '可以返回所有類型的值' } throwStatus().then(success => console.log('成功', success)) .catch(error => console.log('失敗', error));復制代碼

 

 

 

 

//打印結果

成功 可以返回所有類型的值
復制代碼

 

async函數接收到返回的值,發現不是異常或者reject,則判定成功,這里可以return各種數據類型的值,false,NaN,undefined...總之,都是resolve

但是返回如下結果會使async函數判定失敗reject

  1. 內部含有直接使用並且未聲明的變量或者函數。
  2. 內部拋出一個錯誤throw new Error或者返回reject狀態return Promise.reject('執行失敗')
  3. 函數方法執行出錯(🌰:Object使用push())等等...

還有一點,在async里,必須要將結果return回來,不然的話不管是執行reject還是resolved的值都為undefine,建議使用箭頭函數。

其余返回結果都是判定resolved成功執行。

 

 

 

//正確reject方法。必須將reject狀態return出去。 async function PromiseError() { return Promise.reject('has Promise Error'); } //這是錯誤的做法,並且判定resolve,返回值為undefined,並且Uncaught報錯 async function PromiseError() { Promise.reject('這是錯誤的做法'); } PromiseError().then(success => console.log('成功', success)) .catch(error => console.log('失敗', error));復制代碼

 

 

 

我們看到第二行多了個Promise對象打印,不用在意,這個是在Chrome控制台的默認行為,我們平常在控制台進行賦值也是同樣的效果。如果最后執行語句或者表達式沒有return返回值,默認undefined,做個小實驗。

 

 

 

var a = 1;
//undefined
------------------------------------------------------------
console.log(a);
//1
//undefined
------------------------------------------------------------
function a(){ console.log(1) } a(); //1 //undefined ------------------------------------------------------------ function b(){ return console.log(1) } b(); //1 //undefined ------------------------------------------------------------ function c(){ return 1} c(); //1 ------------------------------------------------------------ async function d(){ '這個值接收不到' } d().then(success => console.log('成功',success)); //成功 undefined //Promise { <resolved>: undefined } ----------------------------------------------------------- async function e(){ return '接收到了' } e().then(success => console.log('成功',success)); //成功 接收到了 //Promise { <resolved>: undefined }復制代碼

 

最后一行Promise { <resolved> : undefined } 是因為返回的是console.log執行語句,沒有返回值。

 

 

 

d().then(success => console.log('成功',success)} 等同於 d().then(function(success){ return console.log('成功',success); }); 復制代碼

 

認識完了async,來講講await。

await是什么?

await意思是async wait(異步等待)。這個關鍵字只能在使用async定義的函數里面使用。任何async函數都會默認返回promise,並且這個promise解析的值都將會是這個函數的返回值,而async函數必須等到內部所有的 await 命令的 Promise 對象執行完,才會發生狀態改變。

打個比方,await是學生,async是校車,必須等人齊了再開車。

就是說,必須等所有 await 函數執行完畢后,才會告訴 promise我成功了還是失敗了,執行 then或者 catch

 

 

 

async function awaitReturn() { return await 1 }; awaitReturn().then(success => console.log('成功', success)) .catch(error => console.log('失敗',error))復制代碼

 

在這個函數里,有一個await函數,async會等到await 1 這一步執行完了才會返回promise狀態,毫無疑問,判定resolved

很多人以為await會一直等待之后的表達式執行完之后才會繼續執行后面的代碼,實際上await是一個讓出線程的標志await后面的函數會先執行一遍,然后就會跳出整個async函數來執行后面js棧的代碼。等本輪事件循環執行完了之后又會跳回到async函數中等待await后面表達式的返回值,如果返回值為非promise則繼續執行async函數后面的代碼,否則將返回的promise放入Promise隊列(Promise的Job Queue)

來看個簡單點的例子

 

 

 

const timeoutFn = function(timeout){ return new Promise(function(resolve){ return setTimeout(resolve, timeout); }); } async function fn(){ await timeoutFn(1000); await timeoutFn(2000); return '完成'; } fn().then(success => console.log(success));復制代碼

 

這里本可以用箭頭函數寫方便點,但是為了便於閱讀本質,還是換成了ES5寫法,上面執行函數內所有的await函數才會返回狀態,結果是執行完畢3秒后才會彈出'完成'。

正常情況下,await 命令后面跟着的是 Promise ,如果不是的話,也會被轉換成一個 立即 resolve 的 Promise。

也可以這么寫

 

 

 

function timeout(time){ return new Promise(function(resolve){ return setTimeout(function(){ return resolve(time + 200) },time); }) } function first(time){ console.log('第一次延遲了' + time ); return timeout(time); } function second(time){ console.log('第二次延遲了' + time ); return timeout(time); } function third(time){ console.log('第三次延遲了' + time ); return timeout(time); } function start(){ console.log('START'); const time1 = 500; first(time1).then(time2 => second(time2) ) .then(time3 => third(time3) ) .then(res => { console.log('最后一次延遲' + res ); console.timeEnd('END'); }) }; start();復制代碼

這樣用then鏈式回調的方式執行resolve

 

 

 

//打印結果

START
第一次延遲了500
第二次延遲了700
第三次延遲了900
最后一次延遲1100
END
復制代碼

 

用async/await呢?

 

 

 

async function start() { console.log('START'); const time1 = 500; const time2 = await first(time1); const time3 = await second(time2); const res = await third(time3); console.log(`最后一次延遲${res}`); console.log('END'); } start();復制代碼

達到了相同的效果。但是這樣遇到一個問題,如果await執行遇到報錯呢

 

 

 

async function start() { console.log('START'); const time1 = 500; const time2 = await first(time1); const time3 = await Promise.reject(time2); const res = await third(time3); console.log(`最后一次延遲${res}`); console.log('END'); } start();復制代碼

 

返回reject后,后面的代碼都沒有執行了,以此遷出一個例子:

 

 

 

let last; async function throwError() { await Promise.reject('error'); last = await '沒有執行'; } throwError().then(success => console.log('成功', last)) .catch(error => console.log('失敗',last))復制代碼

 

 

其實
async函數不難,難在錯處理上。

 

上面函數,執行的到await排除一個錯誤后,就停止往下執行,導致last沒有賦值報錯。

async里如果有多個await函數的時候,如果其中任一一個拋出異常或者報錯了,都會導致函數停止執行,直接 reject;

怎么處理呢,可以用try/catch,遇到函數的時候,可以將錯誤拋出,並且繼續往下執行。

 

 

 

let last; async function throwError() { try{ await Promise.reject('error'); last = await '沒有執行'; }catch(error){ console.log('has Error stop'); } } throwError().then(success => console.log('成功', last)) .catch(error => console.log('失敗',last))復制代碼

這樣的話,就可以繼續往下執行了。

 

 

來個🌰練習下

 

 

 

 

function testSometing() { console.log("testSomething"); return "return testSomething"; } async function testAsync() { console.log("testAsync"); return Promise.resolve("hello async"); } async function test() { console.log("test start..."); const testFn1 = await testSometing(); console.log(testFn1); const testFn2 = await testAsync(); console.log(testFn2); console.log('test end...'); } test(); var promiseFn = new Promise((resolve)=> { console.log("promise START..."); resolve("promise RESOLVE"); }); promiseFn.then((val)=> console.log(val)); console.log("===END===")復制代碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM