<dl id="3wz6h"></dl><li id="3wz6h"></li>

      1. <dl id="3wz6h"></dl>

      2. <dl id="3wz6h"><ins id="3wz6h"></ins></dl>

            <dl id="3wz6h"></dl>

            <dl id="3wz6h"><ins id="3wz6h"></ins></dl>
            1. 
              
              <output id="3wz6h"><ins id="3wz6h"><nobr id="3wz6h"></nobr></ins></output>

              <li id="3wz6h"><ins id="3wz6h"></ins></li>
              
              

            2. <output id="3wz6h"><ins id="3wz6h"><nobr id="3wz6h"></nobr></ins></output>
              首頁»JavaScript»重讀 ES6 - async+await 同步/異步方案

              重讀 ES6 - async+await 同步/異步方案

              來源:Jeremy_young 發布時間:2018-08-03 閱讀次數:

                異步編程一直是JavaScript 編程的重大事項。關于異步方案, ES6 先是出現了 基于狀態管理的 Promise,然后出現了 Generator 函數 + co 函數,緊接著又出現了 ES7 的 async + await 方案。

                本文力求以最簡明的方式來疏通 async + await。

                異步編程的幾個場景

                先從一個常見問題開始:一個for 循環中,如何異步的打印迭代順序?

                我們很容易想到用閉包,或者 ES6 規定的 let 塊級作用域來回答這個問題。

              for (let val of [1, 2, 3, 4]) {
                  setTimeout(() => console.log(val),100);
              }
              // => 預期結果依次為:1, 2, 3, 4
              

                這里描述的是一個均勻發生的的異步,它們被依次按既定的順序排在異步隊列中等待執行。

                如果異步不是均勻發生的,那么它們被注冊在異步隊列中的順序就是亂序的。

              for (let val of [1, 2, 3, 4]) {
                  setTimeout(() => console.log(val), 100 * Math.random());
              }
              // => 實際結果是隨機的,依次為:4, 2, 3, 1
              

                返回的結果是亂序不可控的,這本來就是最為真實的異步。但另一種情況是,在循環中,如果希望前一個異步執行完畢、后一個異步再執行,該怎么辦?

              for (let val of ['a', 'b', 'c', 'd']) {
                  // a 執行完后,進入下一個循環
                  // 執行 b,依此類推
              }
              

                這不就是多個異步 “串行” 嗎!

                在回調 callback 嵌套異步操作、再回調的方式,不就解決了這個問題!或者,使用 Promise + then() 層層嵌套同樣也能解決問題。但是,如果硬是要將這種嵌套的方式寫在循環中,還恐怕還需費一番周折。試問,有更好的辦法嗎?

                異步同步化方案

                試想,如果要去將一批數據發送到服務器,只有前一批發送成功(即服務器返回成功的響應),才開始下一批數據的發送,否則終止發送。這就是一個典型的 “for 循環中存在相互依賴的異步操作” 的例子。

                明顯,這種 “串行” 的異步,實質上可以當成同步。它和亂序的異步比較起來,花費了更多的時間。按理說,我們希望程序異步執行,就是為了 “跳過” 阻塞,較少時間花銷。但與之相反的是,如果需要一系列的異步 “串行”,我們應該怎樣很好的進行編程?

                對于這個 “串行” 異步,有了 ES6 就非常容易的解決了這個問題。

              async function task () {
                  for (let val of [1, 2, 3, 4]) {
                      // await 是要等待響應的
                      let result = await send(val);
                      if (!result) {
                          break;
                      }
                  }
              }
              task();
              

                從字面上看,就是本次循環,等有了結果,再進行下一次循環。因此,循環每執行一次就會被暫停(“卡住”)一次,直到循環結束。這種編碼實現,很好的消除了層層嵌套的 “回調地獄” 問題,降低了認知難度。

                這就是異步問題同步化的方案。關于這個方案,如果說 Promise 主要解決的是異步回調問題,那么 async + await 主要解決的就是將異步問題同步化,降低異步編程的認知負擔。

                async + await “外異內同”

                早先接觸這套 API 時,看著繁瑣的文檔,一知半解的認為 async + await 主要用來解決異步問題同步化的。

                其實不然。從上面的例子看到:async 關鍵字聲明了一個 異步函數,這個 異步函數 體內有一行 await 語句,它告示了該行為同步執行,并且與上下相鄰的代碼是依次逐行執行的。

                將這個形式化的東西再翻譯一下,就是:

              1、async 函數執行后,總是返回了一個 promise 對象
              2、await 所在的那一行語句是同步的

                其中,1 說明了從外部看,task 方法執行后返回一個 Promise 對象,正因為它返回的是 Promise,所以可以理解task 是一個異步方法。毫無疑問它是這樣用的:

              task().then((val) => {alert(val)})
                    .then((val) => {alert(val)})
              

                2 說明了在 task 函數內部,異步已經被 “削” 成了同步。整個就是一個執行稍微耗時的函數而已。

                綜合 1、2,從形式上看,就是 “task 整體是一個異步函數,內部整個是同步的”,簡稱“外異內同”。

                整體是一個異步函數 不難理解。在實現上,我們不妨逆向一下,語言層面讓async關鍵字調用時,在函數執行的末尾強制增加一個promise 反回:

              async fn () {
                  let result;
                  // ...
                  //末尾返回 promise
                  return isPromise(result)? 
                          result : Promise.resolve(undefined);
              }
              

                內部是同步的 是怎么做到的?實際上 await 調用,是讓后邊的語句(函數)做了一個遞歸執行,直到獲取到結果并使其 狀態 變更,才會 resolve 掉,而只有 resolve 掉,await 那一行代碼才算執行完,才繼續往下一行執行。所以,盡管外部是一個大大的 for 循環,但是整個 for 循環是依次串行的。

                因此,僅從上述框架的外觀出發,就不難理解 async + await 的意義。使用起來也就這么簡單,反而 Promise 是一個必須掌握的基礎件。

                秉承本次《重讀 ES6》系列的原則,不過多追求理解細節和具體實現過程。我們繼續鞏固一下這個 “形式化” 的理解。

                async + await 的進一步理解

                有這樣的一個異步操作 longTimeTask,已經用 Promise 進行了包裝。借助該函數進行一系列驗證。

              const longTimeTask = function (time) {
                return new Promise((resolve, reject) => {
                  setTimeout(()=>{
                    console.log(`等了 ${time||'xx'} 年,終于回信了`);
                    resolve({'msg': 'task done'});
                  }, time||1000)
                })
              }
              

                async 函數的執行情況

                如果,想查看 async exec1 函數的返回結果,以及 await 命令的執行結果:

              const exec1 = async function () {
                let result = await longTimeTask();
                console.log('result after long time ===>', result);
              }
              // 查看函數內部執行順序
              exec1();
              // => 等了 xx 年,終于回信了
              // => result after long time ===> Object {msg: "task done"}
              
              //查看函數總體返回值
              console.log(exec1());
              // => Promise {[[PromiseStatus]]: "pending",...}
              // => 同上
              

                以上 2 步執行,清晰的證明了 exec1 函數體內是同步、逐行逐行執行的,即先執行完異步操作,然后進行 console.log() 打印。而 exec1() 的執行結果就直接是一個 Promise,因為它最先會蹦出來一串 Promise ...,然后才是 exec1 函數的內部執行日志。

                因此,所有驗證,完全符合 整體是一個異步函數,內部整個是同步的 的總結。

                await 如何執行其后語句?

                回到 await ,看看它是如何執行其后邊的語句的。假設:讓 longTimeTask() 后邊直接帶 then() 回調,分兩種情況:

                1)then() 中不再返回任何東西

                2) then() 中繼續手動返回另一個 promise

              const exec2 = async function () {
                let result = await longTimeTask().then((res) => {
                  console.log('then ===>', res.msg);
                  res.msg = `${res.msg} then refrash message`;
                  // 注釋掉這條 return 或 手動返回一個 promise
                  return Promise.resolve(res);
                });
                console.log('result after await ===>', result.msg);
              }
              exec2();
              // => 情況一 TypeError: Cannot read property 'msg' of undefined
              // => 情況二 正常
              

                首先,longTimeTask() 加上再多得 then() 回調,也不過是放在了它的回調列隊 queue 里了。也就是說,await 命令之后始終是一條 表達式語句,只不過上述代碼書寫方式比較讓人迷惑。(比較好的實踐建議是,將 longTimeTask 方法身后的 then() 移入 longTimeTask 函數體封裝起來)

                其次,手動返回另一個 promise 和什么也不返回,關系到 longTimeTask() 方法最終 resolve 出去的內容不一樣。換句話說,await 命令會提取其后邊的promise 的 resolve 結果,進而直接導致 result 的不同。

                值得強調的是,await 命令只認 resolve 結果,對 reject 結果報錯。不妨用以下的 return 語句替換上述 return 進行驗證。

              return Promise.reject(res);
              

                最后

                其實,關于異步編程還有很多可以梳理的,比如跨模塊的異步編程、異步的單元測試、異步的錯誤處理以及什么是好的實踐。All in all, 限于篇幅,不在此匯總了。最后,async + await 確實是一個很優雅的方案。

              QQ群:WEB開發者官方群(515171538),驗證消息:10000
              微信群:加小編微信 849023636 邀請您加入,驗證消息:10000
              提示:更多精彩內容關注微信公眾號:全棧開發者中心(fsder-com)
              ES6
              網友評論(共0條評論) 正在載入評論......
              理智評論文明上網,拒絕惡意謾罵 發表評論 / 共0條評論
              登錄會員中心
              云南十一选往期