一次性讓你懂async/await,解決回調地獄

什麼是async?

歡迎留言討論編程

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

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

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

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

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

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

async是ES7新出的特性,代表當前函數是異步函數,不會阻塞線程致使後續代碼中止運行。spa

怎麼用

申明以後就能夠進行調用了線程

async function asyncFn() {
  return 'hello world';
}
asyncFn();

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

     

返回的是一個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===")

 

執行結果

 

咱們一步步來解析

首先test()打印出test start...

而後 testFn1 = await testSomething(); 的時候,會先執行testSometing()這個函數打印出「testSometing」的字符串。

以後由於await會讓出線程就會去執行後面的。testAsync()執行完畢返回resolve,觸發promiseFn打印出「promise START...」。

接下來會把返回的Promiseresolve("promise RESOLVE")放入Promise隊列(Promise的Job Queue),繼續執行打印「===END===」。

等本輪事件循環執行結束後,又會跳回到async函數中(test()函數),等待以前await 後面表達式的返回值,由於testSometing() 不是async函數,因此返回的是一個字符串「returntestSometing」。

test()函數繼續執行,執行到testFn2(),再次跳出test()函數,打印出「testAsync」,此時事件循環就到了Promise的隊列,執行promiseFn.then((val)=> console.log(val));打印出「promise RESOLVE」。

以後和前面同樣 又跳回到test函數繼續執行console.log(testFn2)的返回值,打印出「hello async」。

最後打印「test end...」。

加點料,讓testSomething()變成async

 

 

 

async 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===")

 

執行結果

和上一個例子比較發現promiseFn.then((val)=> console.log(val));先於console.log(testFn1);執行,緣由是由於如今testSomething()已是async函數,返回的是一個Promise對象要要等它resolve,因此將當前Promise推入隊列,因此會繼續跳出test()函數執行後續代碼。以後就開始執行Promise的任務隊列了,因此先執行了promise.then((val)=> console.log(val));由於這個Promise對象先推入隊列。

愈來愈多的人正在研究聽說是異步終極編程解決方案的async/await,可是大部分人對這個方法內部怎麼執行的還不是很瞭解,整理了await以後js的執行順序,但願對大家有所幫助

  1. 是一種編寫異步代碼的新方法。以前異步代碼的方案是callback和promise。
  2. 創建在 promise 的基礎上,與promise同樣也是非阻塞的。
  3. async/await 讓異步代碼看起來、表現起來更像同步代碼。這正是其威力所在。

參考文獻:理解 JavaScript 的 async/await

做者: 偏執 ゝ 連接:https://juejin.im/post/5b1ffff96fb9a01e345ba704 來源:掘金 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索