理解 JavaScript 的 async/await

来源:https://www.cnblogs.com/renhaooh/archive/2020/03/30/12600083.html
-Advertisement-
Play Games

說明:轉載至自 邊城的理解 JavaScript 的 async/await async 和 await 在乾什麼 任意一個名稱都是有意義的,先從字面意思來理解。async 是“非同步”的簡寫,而 await 可以認為是 async wait 的簡寫。所以應該很好理解 async 用於申明一個 fun ...


說明:轉載至自 邊城理解 JavaScript 的 async/await

async 和 await 在乾什麼

任意一個名稱都是有意義的,先從字面意思來理解。async 是“非同步”的簡寫,而 await 可以認為是 async wait 的簡寫。所以應該很好理解 async 用於申明一個 function 是非同步的,而 await 用於等待一個非同步方法執行完成。

另外還有一個很有意思的語法規定,await 只能出現在 async 函數中。然後細心的朋友會產生一個疑問,如果 await 只能出現在 async 函數中,那這個 async 函數應該怎麼調用?

如果需要通過 await 來調用一個 async 函數,那這個調用的外面必須得再包一個 async 函數,然後……進入死迴圈,永無出頭之日……

如果 async 函數不需要 await 來調用,那 async 到底起個啥作用?

async 起什麼作用

這個問題的關鍵在於,async 函數是怎麼處理它的返回值的!

我們當然希望它能直接通過 return 語句返回我們想要的值,但是如果真是這樣,似乎就沒 await 什麼事了。所以,寫段代碼來試試,看它到底會返回什麼:

async function testAsync() {
    return "hello async";
}

const result = testAsync();
console.log(result);

 

看到輸出就恍然大悟了——輸出的是一個 Promise 對象。

c:\var\test> node --harmony_async_await .
Promise { 'hello async' }

所以,async 函數返回的是一個 Promise 對象。從文檔中也可以得到這個信息。async 函數(包含函數語句、函數表達式、Lambda表達式)會返回一個 Promise 對象,如果在函數中 return 一個直接量,async 會把這個直接量通過 Promise.resolve() 封裝成 Promise 對象。

async 函數返回的是一個 Promise 對象,所以在最外層不能用 await 獲取其返回值的情況下,我們當然應該用原來的方式:then() 鏈來處理這個 Promise 對象,就像這樣

testAsync().then(v => {
    console.log(v);    // 輸出 hello async
});

 

現在回過頭來想下,如果 async 函數沒有返回值,又該如何?很容易想到,它會返回 Promise.resolve(undefined)

聯想一下 Promise 的特點——無等待,所以在沒有 await 的情況下執行 async 函數,它會立即執行,返回一個 Promise 對象,並且,絕不會阻塞後面的語句。這和普通返回 Promise 對象的函數並無二致。

那麼下一個關鍵點就在於 await 關鍵字了。

await 到底在等啥

一般來說,都認為 await 是在等待一個 async 函數完成。不過按語法說明,await 等待的是一個表達式,這個表達式的計算結果是 Promise 對象或者其它值(換句話說,就是沒有特殊限定)。

因為 async 函數返回一個 Promise 對象,所以 await 可以用於等待一個 async 函數的返回值——這也可以說是 await 在等 async 函數,但要清楚,它等的實際是一個返回值。註意到 await 不僅僅用於等 Promise 對象,它可以等任意表達式的結果,所以,await 後面實際是可以接普通函數調用或者直接量的。所以下麵這個示例完全可以正確運行

function getSomething() {
    return "something";
}

async function testAsync() {
    return Promise.resolve("hello async");
}

async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}

test();

 

await 等到了要等的,然後呢

await 等到了它要等的東西,一個 Promise 對象,或者其它值,然後呢?我不得不先說,await 是個運算符,用於組成表達式,await 表達式的運算結果取決於它等的東西。

如果它等到的不是一個 Promise 對象,那 await 表達式的運算結果就是它等到的東西。

如果它等到的是一個 Promise 對象,await 就忙起來了,它會阻塞後面的代碼,等著 Promise 對象 resolve,然後得到 resolve 的值,作為 await 表達式的運算結果。

看到上面的阻塞一詞,心慌了吧……放心,這就是 await 必須用在 async 函數中的原因。async 函數調用不會造成阻塞,它內部所有的阻塞都被封裝在一個 Promise 對象中非同步執行。

async/await 幫我們幹了啥

作個簡單的比較

上面已經說明瞭 async 會將其後的函數(函數表達式或 Lambda)的返回值封裝成一個 Promise 對象,而 await 會等待這個 Promise 完成,並將其 resolve 的結果返回出來。

現在舉例,用 setTimeout 模擬耗時的非同步操作,先來看看不用 async/await 會怎麼寫

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

takeLongTime().then(v => {
    console.log("got", v);
});

 

如果改用 async/await 呢,會是這樣

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

async function test() {
    const v = await takeLongTime();
    console.log(v);
}

test();

 

眼尖的同學已經發現 takeLongTime() 沒有申明為 async。實際上,takeLongTime() 本身就是返回的 Promise 對象,加不加 async 結果都一樣,如果沒明白,請回過頭再去看看上面的“async 起什麼作用”。

又一個疑問產生了,這兩段代碼,兩種方式對非同步調用的處理(實際就是對 Promise 對象的處理)差別並不明顯,甚至使用 async/await 還需要多寫一些代碼,那它的優勢到底在哪?

async/await 的優勢在於處理 then 鏈

單一的 Promise 鏈並不能發現 async/await 的優勢,但是,如果需要處理由多個 Promise 組成的 then 鏈的時候,優勢就能體現出來了(很有意思,Promise 通過 then 鏈來解決多層回調的問題,現在又用 async/await 來進一步優化它)。

假設一個業務,分多個步驟完成,每個步驟都是非同步的,而且依賴於上一個步驟的結果。我們仍然用 setTimeout 來模擬非同步操作:

/**
 * 傳入參數 n,表示這個函數執行的時間(毫秒)
 * 執行的結果是 n + 200,這個值將用於下一步驟
 */
function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

 

現在用 Promise 方式來實現這三個步驟的處理

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();

// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

 

輸出結果 result 是 step3() 的參數 700 + 200 = 900doIt() 順序執行了三個步驟,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 計算的結果一致。

如果用 async/await 來實現呢,會是這樣

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

 

結果和之前的 Promise 實現是一樣的,但是這個代碼看起來是不是清晰得多,幾乎跟同步代碼一樣

還有更酷的

現在把業務要求改一下,仍然是三個步驟,但每一個步驟都需要之前每個步驟的結果。

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(m, n) {
    console.log(`step2 with ${m} and ${n}`);
    return takeLongTime(m + n);
}

function step3(k, m, n) {
    console.log(`step3 with ${k}, ${m} and ${n}`);
    return takeLongTime(k + m + n);
}

 

這回先用 async/await 來寫:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time1, time2);
    const result = await step3(time1, time2, time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms

 

 

除了覺得執行時間變長了之外,似乎和之前的示例沒啥區別啊!別急,認真想想如果把它寫成 Promise 方式實現會是什麼樣子?

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => {
            return step2(time1, time2)
                .then(time3 => [time1, time2, time3]);
        })
        .then(times => {
            const [time1, time2, time3] = times;
            return step3(time1, time2, time3);
        })
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();

 

 

有沒有感覺有點複雜的樣子?那一堆參數處理,就是 Promise 方案的死穴—— 參數傳遞太麻煩了,看著就暈!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 作用 用來保存所有傳遞給函數的實際參數 註意點 每個函數都有arguements,實際上它就是一個偽數組 舉例 自定義函數,傳入任意個數值,返回它們最終的和 代碼 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <titl ...
  • HTML 字元實體/HTML 中的預留字元必須被替換為字元實體,一些在鍵盤上找不到的字元也可以使用字元實體來替換。 在 HTML 中,某些字元是預留的。您不能使用包含這些字元的文本。在 HTML 中不能使用小於號(<)和大於號(>),這是因為瀏覽器會誤認為它們是標簽。如果希望正確地顯示預留字元,我們 ...
  • TypeScript的原始數據類型 String Number boolean null undefined enum symbol // Number var num:number=1; // 整數 num = 2.5; // 小數 num = 0xf00d; // 十六進位 num = 0b01 ...
  • <script>定義了客戶端腳本;<noscript>定義了不支持腳本瀏覽器輸出的文本;html <script> 標簽:<script> 標簽用於定義客戶端腳本,比如 JavaScript;<script> 元素既可包含腳本語句,也可通過 src 屬性指向外部腳本文件;JavaScript 最常用... ...
  • 什麼是HTML 顏色?它採用的是 RGB 顏色,是通過對紅(R)、綠(G)、藍(B)三個顏色通道的變化以及它們相互之間的疊加來得到各式各樣的顏色的,RGB即是代表紅、綠、藍三個通道的顏色。HTML 顏色由一個十六進位符號來定義,這個符號由紅色、綠色和藍色的值組成(RGB);每種顏色的最小值是0(十六 ...
  • 非同步 1.知道哪部分代碼是非同步的; 2.必須要知道非同步程式之中的數據或者賦值的變數在哪裡可用; js:單線程語言 上面一行代碼不執行結束,下麵一行代碼不會執行; 單線程 多線程 js中存在很大需要長時間等待才會執行的代碼; 非同步 :在js中需要消耗大量時間等待某些程式執行的時候,會讓正常程式繼續執行 ...
  • 瞭解Ajax 就是js這個語言和服務端交互的手段 無刷新的頁面請求處理 區分表單提交 Ajax 即“Asynchronous Javascript And XML”(非同步 JavaScript 和 XML),是指一種創建互動式、快速動態網頁應用的網頁開發技術,無需重新載入整個網頁的情況下,能夠更新部 ...
  • IT行業作為當今發展前途最大的幾大行業之一,獲得了前所未有的廣泛關註,且隨著科技的發展,這一趨勢將更加明顯。 為了爭奪IT技術人才,很多公司都會提供更高的薪資來吸引高水準的開發人員。 對於IT行業新人來說,想要進入IT行業找到滿意的工作,首先應該選擇適合的職位。 那麼IT行業究竟有哪些職位呢?哪些崗 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...