天天看點

async 與 await 的用法詳解asyncawait注意事項

文章目錄

  • async
    • 概念
    • 傳回值
  • await
    • 概念
    • 用法
    • 傳回結果
    • 匿名函數
  • 注意事項
    • 非 await 部分
    • await 内部
    • 函數嵌套

async

概念

用于聲明異步函數,傳回值為一個

Promise

對象,它以類似 同步 的方式來寫異步方法,文法與聲明函數類似,例如:

async function fn() {
    console.log('Hello world!');
}

console.log(fn().constructor); // Promise()
// 這裡證明其傳回值為一個 Promise 對象;
           

傳回值

也許這裡會有疑問,傳回值是 Promise 對象,那麼函數本身定義的傳回值跑到哪裡去了呢?其實,熟悉 Promise 的就知道其異步結果是通過

.then()

或者

.catch()

方法來擷取并進行進一步處理的,這樣一個道理,定義的異步函數中的傳回值會當成

resolve

狀态來處理,一般用

.then()

方法處理,而如果定義的異步函數抛出錯誤,例如變量未定義,則會被當做

reject

狀态來處理,一般使用

.catch()

方法來處理;

舉例:

// 使用 .then() 的情況
async function fn1() {
    return 'Hello world!';
}

fn1().then(function(res) {
    console.log(res);
});
// Hello world!

// 使用 .catch() 的情況
async function fn2() {
    console.log(aaa); // 這裡的變量 aaa 未定義,為了制造錯誤
}

fn2().catch(function(error) {
    console.log(error);
});
// ReferenceError: aaa is not defined
           

假如是既有傳回值,又有錯誤的話,來看看結果如何:

async function fn3(){
    console.log(aaa); // aaa 依然未定義;
    return 'Hello world!';
}

fn3().then(function(res){
    console.log(res);
}).catch(function(error){
    console.log(error);
});
// ReferenceError: aaa is not defined
           

結果證明隻會執行

reject

狀态的情況下的語句,忽略了

resolve

時的代碼,是以此處值得 注意;

await

概念

用法顧名思義,有 等待 的意思,文法為:

var value = await myPromise();
           

所謂 等待 其實就是指暫停目前

async function

内部語句的執行,等待後面的

myPromise()

處理完傳回結果後,繼續執行

async function

函數内部的剩餘語句;

myPromise()

是一個 Promise對象,而自定義的變量

value

則用于擷取 Promise 對象傳回的 resolve 狀态值;

用法

值得 注意 的是,

await

必須在

async function

内使用,否則會提示文法錯誤;如果 await 後面跟的是其他值,則直接傳回該值:

async function fn() {
    console.log(1);
    var result = await new Promise(function(resolve, reject) {
        setTimeout(function(){
            resolve(2);
        }, 2000);
    });
	console.log(result);
    console.log(3);
    console.log(await 4); // 4 會被直接傳回
}
fn();
// 1
// 2 (2 秒後輸出)
// 3
// 4
           

如果不用擷取傳回值,也可以直接執行語句:

async function fn() {
    console.log(1);
    await new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log(2);
            resolve(0);
        }, 2000);
    });
    console.log(3);
}
fn();
// 1
// 2 (2 秒後)
// 3
           

傳回結果

如之前所說,await 會等到後面的 Promise 傳回結果 後才會執行 async 函數後面剩下的語句,也就是說如果 Promise 不傳回結果(如 resolve 或 reject),後面的代碼就不會執行,例如:

async function fn() {
    console.log(1);
    await new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log(2);
        }, 2000);
    });
    console.log(3);
}
fn();
// 1
// 2 (2 秒後輸出,并且後面不會繼續輸出 3)
           

這裡可以了解為函數會一直等待 await 傳回結果(resolve / reject)才會執行剩下的語句,沒有傳回結果就會一直等下去,也就一直等不到剩下的語句執行了(還挺癡情-_-);

如果 await 後面的 Promise 傳回一個

reject

狀态的結果的話,則會被當成錯誤在背景抛出,例如:

async function fn() {
    console.log(1);
    var result = await new Promise(function(resolve, reject) {
        setTimeout(function() {
            reject(2);
        }, 2000);
    });
    console.log(3);
}
fn();
// 1
// Uncaught (in promise) 2 (2 秒後輸出)
           

如上,2 秒後會抛出出錯誤,并且 3 這個數并沒有被輸出,說明後面的執行也被忽略了;

匿名函數

async 也可以用于申明匿名函數用于不同場景,或者嵌套使用 async 函數,如

await async

的形式,隻是要在 await 後面使用 async 形式的函數的話,需要這個函數立即執行且有傳回值;

let fn = async function() {
    let a = await (async function() {
        console.log(1);
        return 2;
    })();
    console.log(a);

    async function fn2() {
        return 3;
    }
    console.log(await fn2());
}
fn();
// 1
// 2
// 3
           

另外,await 後面的 Promise 傳回的 reject, 也可以被該 async 函數傳回的 Promise 對象以 reject 狀态擷取,例如:

async function fn() {
    console.log(1);
    var result = await new Promise(function(resolve, reject) {
        setTimeout(function() {
            reject(2);
        }, 2000);
    });
    console.log(3);
}
fn().catch(function(error) {
    console.log(error);
});
// 1
// 2 (2 秒後輸出)
           

這種情況就不會以錯誤抛出,直接對異常值進行了處理,并且最後同樣沒有輸出數字 3,即後面的代碼依然被忽略了;

注意事項

非 await 部分

async/await

函數以同步的方式書寫異步函數确實友善了不少場景,如定義所講,函數内部遇到

await

會等到傳回結果再繼續執行下去,也就是說,非 await 部分仍然會以正常的異步或同步方式執行,例如遇到

setTimeout()

就會放入任務隊列等待同步語句執行完後再執行;

比如以下情況:

async function fn() {
    console.log(0);
    
    await new Promise(resolve => {
        setTimeout(() => {
            console.log(1);
            resolve();
        }, 1000);
    });

    setTimeout(() => {
        console.log(2);
    }, 0);

    console.log(3);
}

fn();
// 0
// 1(2 秒後)
// 3
// 2
           

await 内部

雖然說函數會等待 await 傳回結果在繼續執行,但是 await 内部的代碼也依然按正常的同步和異步執行,例如:

async function fn() {
    console.log(0);
    
    setTimeout(() => {
        console.log(1);
    }, 0);

    await new Promise(resolve => {
        setTimeout(() => {
            console.log(2);
        }, 0);

        console.log(3);

        setTimeout(() => {
            console.log(4);
            resolve();
        }, 1000);

        setTimeout(() => {
            console.log(5);
        }, 0);
    });

    setTimeout(() => {
        console.log(6);
    }, 0);
    console.log(7);
}

fn();
// 0
// 3
// 1
// 2
// 5
// 4(2 秒後)
// 7
// 6
           

上面的代碼中傳回結果的函數

resolve()

是在

setTimeout()

這個 異步任務 中,是以其被丢到事件隊列中等待 2 秒再執行,由于此時 await 還未傳回結果,是以還不會去執行 await 以外的代碼(輸出 7、6),而是先執行同為異步任務、但延時較短的輸出 1、2、5 的代碼;2 秒後結果傳回了,就會繼續正常執行 await 以外的同步任務和異步任務了;

但是假如 await 代碼内傳回結果的函數(resolve() 或 reject())是在 同步任務 中執行的話,情況就有些不一樣了,例如:

async function fn() {
    console.log(0);

    setTimeout(() => {
        console.log(1);
    }, 0);

    await new Promise(resolve => {
        setTimeout(() => {
            console.log(2);
        }, 0);

        console.log(3);
        resolve();
        console.log(4);

        setTimeout(() => {
            console.log(5);
        }, 0);
    });

    setTimeout(() => {
        console.log(6);
    }, 0);
    console.log(7);
}

fn();
// 0 
// 3
// 4
// 7
// 1
// 2
// 5
// 6
           

由于同步任務 先于 異步任務執行的機理,在同步任務執行過程中依次輸出了 0、3 後,就立即執行了

resolve()

使得 await 得到了傳回結果,再往後就繼續同步的輸出了 4,但是輸出 5 的代碼是異步任務,與輸出 1、2 的代碼一并放入任務隊列,此時由于 await 傳回了結果,是以可以執行 await 以外的代碼了,輸出 6 是異步任務,于是先輸出了同步任務的 7,同步任務都執行完了,最後執行任務隊列中的異步任務,按之前進入隊列的順序,就是依次輸出 1、2、5、6,所有代碼運作結束;

函數嵌套

當 async 函數中嵌套着其他 async 函數時,執行過程可能又有些和預想的不一樣,先來看下面的例子:

async function fn() {
    console.log(0);

    setTimeout(() => {
        console.log(1);
    }, 0);

    (async function() {
        console.log(2);
    
        setTimeout(() => {
            console.log(3);
        }, 0);

        await new Promise(res => setTimeout(res, 1000))

        setTimeout(() => {
            console.log(4);
        }, 1000);

        console.log(5);
    })()

    console.log(6)
}

fn();
// 0
// 2
// 6
// 1
// 3
// 5(1 秒後)
// 4(再等 1 秒後)
           

也許會疑惑,不是說 async 函數會等到 await 傳回結果後再繼續執行嗎,為何就先輸出 6 了?其實不要混淆概念,确實 async 函數内部是這樣幹的(3 後 1秒輸出 5、4),但 async 函數它自身執行時依然是正常的同步任務執行,也就是雖然内部的 async 函數會等待其 await 傳回結果才繼續執行後面的代碼,但外部的 async 函數可不會等待内部的那個 await,會照常執行(你不是我的菜,天涯何處無芳草╮(╯▽╰)╭);

如果确實需要等待這個嵌套的 async 函數執行完再執行剩下的代碼,那麼前面加個 await 就行了,原理是也是可行的,因為 async 函數就是傳回的一個 Promise 函數,代碼如下:

async function fn() {
    console.log(0);

    setTimeout(() => {
        console.log(1);
    }, 0);

    await (async function() {
        console.log(2);
    
        setTimeout(() => {
            console.log(3);
        }, 0);

        await new Promise(res => setTimeout(res, 1000))

        setTimeout(() => {
            console.log(4);
        }, 1000);

        console.log(5);
    })()

    console.log(6)
}

fn();
// 0
// 2
// 1
// 3
// 5(1 秒後)
// 6
// 4(再等 1 秒後)
           

這裡也要 注意,假如嵌套的 async 函數中的 await 不傳回結果,并且沒有在嵌套的 async 函數前面添加 await,那麼外部的 async 函數内部剩餘的代碼也不會執行;

繼續閱讀