首頁 > 軟體

async-await消滅非同步回撥範例詳解

2022-07-26 18:03:14

引言

本篇,帶你讀懂async~await間的浪漫。

關於非同步處理問題,ES5的回撥讓我們陷入回撥地獄輪迴,後來ES6的Promise(Promise不瞭解?點這瞭解)讓我們脫離輪迴,終於,ES7的async-await帶我們走向光明。今天我們就來學習一夏 async-await,看看與Promise有何聯絡和區別。

一、走進Async-await原理

1、原理1

async函數返回一個 Promise 物件,可以使用then方法新增回撥函數。舉例說明:

// async返回的是Promise物件?
async function testAsync() {
    return 'hello';//上篇文章Promise物件的返回值如果不是Promise,會通過Promise.resolve()轉化為Promise,再進行處理
}
const result = testAsync()
console.log(result);//Promise { 'hello' }  說明async返回的是Promise物件

那既然async返回的是Promise物件,那麼async後面的函數可以接.then()或者.catch()...嘛?我們試一試就知道了。

// async返回的是Promise物件,並且可以接Promise的方法?
async function testAsync() {
    // await await等待還是promise物件
    return 'hello'
}
testAsync()
    .then((result)=>{
        console.log(result);
    })
    .catch((error)=>{
        console.log(error);
    })
//hello  媽耶!列印了!說明async返回的是Promise物件,並且可以接Promise的方法,並且!!!預設狀態是resolved的

上面程式碼說明,async函數內部return語句返回的值,會成為then方法回撥函數的引數

2、原理2

當async函數內部丟擲錯誤的時候,會導致返回的 Promise 物件變為reject狀態。丟擲的錯誤物件會被.then()方法的第二個回撥函數接收或者.catch()方法回撥函數接收到。

// async函數內部丟擲錯誤或者Promise狀態為reject
async function testError(){
    //throw new Error('出錯啦~~');
    await Promise.reject('出錯了');//await前面有return和沒有return效果一樣
} 
testError()
    // .then(()=>{},(error)=>{console.log(error);})
    .catch(error=>{console.log(error);})
//Error: 出錯啦~~

3、原理3

await命令後面是一個 Promise 物件,返回該物件的結果。如果不是 Promise 物件,就直接返回對應的值。程式碼說明:

// await
async function getName(){
    // return '來自星星的你';
    return await '來自星星的你';//上面直接return等價於這個return
}
getName()
    .then(result=>{console.log(result);})
//來自星星的你

4、原理4

await的使用,必須要有async。這便是async-await的浪漫所在了:async返回的是一個Promise物件,await等待的就是這個Promise物件,所以await不能沒有async(但是async可以沒有await)。有沒有被浪漫到?反正我是醉了。如果await沒有async會怎麼樣?報錯:

// await沒有async會報錯
function testAwait(){
    return await '西紅柿炒辣椒'
}
testAwait()
    .catch(error=>{
        console.log(error);
    })
//SyntaxError: await is only valid in async function

二、深入Async-await規則

1、async封裝Promise

// async封裝Promise
async function fn1() {
    return '喜羊羊與灰太狼';// //相當於return Promise.resolve('喜羊羊與灰太狼')
    const data = await fn1();//接收data值
}
fn1()//執行async函數,返回的是一個Promise物件
    .then(data => {
        console.log('content =', data)
    })
​
//content = 喜羊羊與灰太狼

2、await相當於then

// await---.then()
async function getName(){
    const operate=Promise.resolve('白雪公主')//執行函數
    const name= await operate //await相當於Promise的then  operate.then(name=>{})
    console.log('name:',name)
}
getName();
( async function(){
    const person=await '七個小矮人' //await Promise.resolve('七個小矮人') await後面不跟Promise,也會被封裝成Promise
    console.log('person:',person)//400
})();//自執行函數
​
//name: 白雪公主
//person: 七個小矮人

3、多個await時,按時序執行

當函數執行的時候,一旦遇到await就會先返回,等到非同步操作完成,再接著執行函數體內後面的語句。任何一個await語句後面的 Promise 物件變為reject狀態,那麼整個async函數都會中斷執行。

async function testOrder() {
    await Promise.reject('出錯了')//UnhandledPromiseRejectionWarning: 出錯了
    await Promise.resolve('hello world'); // 不會執行
}
testOrder();

4、try…catch相當於catch

如果希望即使前一個非同步操作失敗,也不要中斷後面的非同步操作。可將第一個await放在try...catch結構裡面,這樣不管這個非同步操作是否成功,第二個await都會執行。

// try...catch
!(async function () {
    const testError = Promise.reject('出錯啦~~~')//rejected狀態
    // const testError=throw new Error('出錯啦~~~');
    try {
        const result = await testError; //await相當於then,但是reject不會觸發then
        console.log('success:'+result) //不會輸出,因為const result = await testError被報錯,被catch捕獲
    } catch (error) {
        console.error('error:'+error)//try...catch 相當於Promise的catch
    }
​
})()
//error:出錯啦~~~

當await後面是Promise物件的時候,我們也可直接在await後面直接.catch捕獲錯誤:

async function testError() {
    await Promise.reject('出錯了')
        .catch(error => console.log(error));//這裡捕獲錯誤,不會影響下一個await執行
    
    return await Promise.resolve('hello world');
}
​
testError()
    .then(result => console.log(result))

三、解析Async-await語法

我們淺淺看一個面試題:

// 面試題
function getJSON() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(2);
            resolve(2)
        }, 2000)
    })
}
async function testAsync() {
    await getJSON()
    console.log(3);
}
testAsync()
//2
//3

問題當然不會問列印順序啦,問題是將async await語句解析翻譯為Promise?

根據現在的知識面,我們必須知道:

(1)await不能單獨出現,其函數前面一定要有async。

(2)await會幹兩件事:

第一,將寫在await後面的程式碼放到async建立的那個Promise裡面執行。

第二、將寫在await下面的程式碼放到前一個建立的那個Promise物件的.then裡面執行。

(3)await返回的也是Promise物件,他只是把await下面的程式碼放到了await返回的promise的.then裡面執行。

這樣的話,是不是如魚得水了。翻譯如下:

function getJSON() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(2);
            resolve(2)
        }, 2000)
    })
}
// 編譯成Promise原理
function testAsync() {
    return Promise.resolve().then(() => {
        return getJSON();
    })
        .then(() => {
            console.log(3);
​
        })
}
testAsync()

四、拓展Async-await應用

1、場景1

你學廢async-await了嘛?還記得上一篇開篇的回撥地獄嘛?我們通過Promise解決回撥是這樣的:

// Promise解決方式
function doCallback(n) {
    var myPromise = new Promise(function (resolve, reject) {   
        //處理非同步任務
        var flag = true;
        setTimeout(function () {
            if (flag) {
                resolve(n)
            }
            else {
                reject('失敗')
            }
        },0)
    })
    return myPromise;
}
​
doCallback(1)
    .then((result) => { //then是成功執行的方法 返回的還是一個Promise物件
        console.log(result);//列印張三  res是執行
        return fn(2);
    })
    .then((result) => {
        console.log(result);
        return fn(3)
    })
    .then((result) => {
        console.log(result);
        return fn(4)
    })
    .then((result) => {
        console.log(result);
    })
    .catch((result) => { //catch是失敗執行的方法
        console.log(result);
    })
    //好多.then,形成.then鏈啦
//1
//2
//3
//4

通過以上Promise方法,可以明顯解決回撥地獄“向右移”的浮誇表現,但是,Promise是基於 then, catch 的鏈式呼叫,但也是基於回撥函數。.then鏈多多少少還是違背原生程式碼,顯得也不是很優雅。作為回撥終極武器,async-await更加貼近於原生程式碼,我們看一下吧:

//封裝一個返回promise的非同步任務
function doCallback(str) {
    var myPromise = new Promise(function (resolve, reject) {
        var flag = true;
        setTimeout(function () {
            if (flag) {
                resolve(str)
            } else {
                reject('處理失敗')
            }
        })
    })
    return myPromise;
}
​
//封裝一個執行上述非同步任務的async函數
async function testAsync() {
    var result1 = await doCallback(1);  //await直接拿到fn()返回的promise的資料,並且賦值給result
    var result2 = await doCallback(2);  //await 後面的程式碼,都可以看做是非同步回撥 callback 裡的內容,都是非同步的
    var result3 = await doCallback(3);
    var result4 = await doCallback(4);
    console.log(result1);
    console.log(result2);
    console.log(result3);
    console.log(result4);
}//這樣是不是簡潔優雅多了呢?
//執行函數
testAsync();
//1
//2
//3
//4

有了 async-await、promise 還有必要學習嗎?通過上面async-await的解決方案可以瞧見,async / await 和 Promise 並不互斥,二者相輔相成。同時async / await 並不能改變非同步的本質( js是單執行緒的,非同步需要回撥,都是要基於 event loop 來實現(什麼是event loop?關注我,等我文章~))。

總結

現在知道了,async-await是promise的語法糖了吧,不僅讓我們書寫程式碼時更加流暢,而且增強了程式碼的可讀性。特別注意的是:雖然async-await 是建立在 Promise機制之上的,但是並不能取代其地位,他們兩者相輔相成,息息相關。

其實async-await不止是Promise的語法糖,還是Generator的語法糖,Generator是什麼?更多關於async-await消滅非同步回撥的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com