摘要:簡介指的是兩個關鍵字,是引入的新標準,關鍵字用于聲明函數,關鍵字用來等待異步必須是操作,說白了就是的語法糖。最后希望大家在讀過異步發展流程這個系列之后,對異步已經有了較深的認識,并可以在不同情況下游刃有余的使用這些處理異步的編程手段。
閱讀原文
這篇文章是異步發展流程系列的最后一篇,可能會涉及 Promise、Generators、co 等前置知識,如果對這些不是很了解可以看這個系列的前三篇:
異步發展流程 —— Promise 的基本使用
異步發展流程 —— 手寫一個符合 Promise/A+ 規范的 Promise
異步發展流程 —— Generators + co 讓異步更優雅
如果已經具備這些前置知識,那我們繼續看看今天的主角,JavaScript 異步編程的終極大招 async/await。
async/await 簡介async/await 指的是兩個關鍵字,是 ES7 引入的新標準,async 關鍵字用于聲明 async 函數,await 關鍵字用來等待異步(必須是 Promise)操作,說白了 async/await 就是 Generators + co 的語法糖。
async/await 和 Generators + co 的寫法非常的相似,只是把用于聲明 Generator 函數的 * 關鍵字替換成了 async 并寫在了 function 關鍵字的前面,把 yield 關鍵字替換成了 await;另外,async 函數是基于 Promise 的,await 關鍵字后面等待的異步操作必須是一個 Promise 實例,當然也可以是原始類型的值,只不過這時的執行效果等同于同步,與 Generator 不同的是,await 關鍵字前可以使用變量去接收這個正在等待的 Promise 實例執行后的結果。
async 函數的基本用法async 函數返回一個 Promise 實例,可以使用 then 方法添加回調函數。當函數執行的時候,只要遇到 await 就會等待,直到 await 后面的同步或異步操作完成,再接著執行函數體內后面的語句。
1、async 函數聲明async 的聲明方式大概有以下幾種:
// async 函數聲明 // 函數聲明 async function fn() {} // 函數表達式 const fn = async function() {}; // 箭頭函數 const fn = async () => {}; // 作為對象的方法 let obj = { async fn() {} }; // 作為 class 的方法 class Person(name) { constructor () { this.name = name; } async getName() { const name = await this.name; return name; } }
在上一篇介紹 Generators + co 的文章中我們舉了一個例子,使用 NodeJS 的 fs 模塊連續異步讀文件,第一個文件名為 a.txt,讀到的內容為 b.txt,作為要讀的第二個文件的文件名,繼續讀 b.txt 后將讀到的內容 “Hello world” 打印出來。
我們來使用 async/await 的方式來實現一下:
// async 函數實現文件讀取 // 引入依賴 const fs = require("fs"); const util = require("util"); // 將 fs.readFile 轉換成 Promise const readFile = util.promisify(fs.readFile); // 聲明 async 函數 async function read(file) { let aData = await readFile(file, "utf8"); let bData = await readFile(aData, "utf8"); return bData; } // 調用 async 函數 read("a.txt").then(data => { console.log(data); // Hello world });
其實對比上一篇文章 Generator 的案例,與 Generator 函數一樣,寫法像同步,執行是異步,不同的是我們即沒有手動調用 next 方法,也沒有借助 co 庫,其實是 async 函數內部集成了類似于 co 的執行器,幫我們在異步完成后自動向下執行代碼,所以說 async/await 是 Generators + co 的語法糖。
2、async 函數錯誤處理async 函數內部如果執行錯誤可以有三種方式進行錯誤處理:
在 await 后面的 Promise 實例使用 then 方法錯誤的回調或 catch 方法進行錯誤處理;
如果有多個 await,可以在 async 函數執行完后使用 catch 方法統一處理;
由于 async 內部代碼是同步的寫法,多個 await 的情況也可以使用 try...catch... 進行處理。
需要注意的是,如果在 async 函數內部使用了 try...catch... 又在函數執行完后使用了 catch,錯誤會優先被同步的 try...catch... 捕獲到,后面的 catch 就不會再捕獲了。
// async 函數異常捕獲 // 第一種 async function fn() { let result = await Promise.reject("error").catch(err => { console.log(err); }); } fn(); // error // 第二種 async function fn() { try { let val1 = await Promise.reject("error"); let val2 = await Promise.resolve("success"); } catch (e) { console.log(e); } } fn(); // error // 第三種 async function fn() { let val1 = await Promise.resolve("success"); let val2 = await Promise.reject("error"); } fn().catch((err => console.log(err))); // error3、await 異步并發
在 async 函數中,如果有多個 await 互不依賴,這種情況下如果執行一個,等待一個完成,再執行一個,再等待完成,這樣是很浪費性能的,所以我們要把這些異步操作同時觸發。
假設我們異步讀取兩個文件,且這兩個文件不相關,我可以使用下面的方式來實現:
// await 異步并發 // 前置 const fs = require("fs"); const util = require("util"); const readFile = util.promisify(fs.readFile); // 需要改進的 async 函數 async function fn() { let aData = await readFile("a.txt", "utf8"); let bData = await readFile("b.txt", "utf8"); return [aData, bData]; } fn(); // 在 async 函數外部觸發異步 let aDataPromise = readFile("a.txt", "utf8"); let bDataPromise = readFile("b.txt", "utf8"); async function fn() { let aData = await aDataPromise; let bData = await bDataPromise; return [aData, bData]; } fn(); // 使用 Promise.all async function fn() { let dataArr = await Promise.all( readFile("a.txt", "utf8"), readFile("a.txt", "utf8") ); return dataArr; } fn();4、使用 async/await 的注意點
使用 async/await 應注意以下幾點:
對 await 習慣性錯誤處理;
await 命令后互不依賴的異步應同時觸發;
async 函數中,函數的執行上/下文發生變化時,不能使用 await(如使用 forEach 循環的回調中)。
針對第一點,在 async 函數中 await 命令后面大多情況下是 Promise 異步操作,運行結果可能出現錯誤并調用 reject 函數,最好對這個 await 語句進行錯誤處理,具體方式參照 async 函數基本用法中關于錯誤處理的內容。
針對第二點,如果兩個或多個 await 命令后的異步操作沒有依賴關系,執行時,需先觸發第一個,等待異步完成,再觸發第二個,再等異步完成,依次類推,這樣比較耗時,性能不好,所以應該將這些異步操作同時觸發,觸發方式參照 async 函數基本用法中的 await 異步并發的內容。
針對第三點,如果聲明一個 async 函數并傳入一個數組,數組里面存儲的都是 Promise 實例,若使用 forEach 循環數組,由于函數的執行上/下文發生了變化,此時使用 await 命令會報錯。
// 循環內使用 await // 創建 Promise 實例 let p1 = Promise.resolve("p1 success"); let p2 = Promise.resolve("p2 success"); let p3 = Promise.resolve("p3 success"); // async 函數 async function fn(promises) { promise.forEach(function (promise) { await promise; }); } fn([p1, p2, p3]); // 執行時報錯 // 修改方式 async function fn(promises) { for(let i = 0; i < promises.length; i++) { await pormises[i]; } } fn([p1, p2, p3]); // 正常執行
async/await 的實現原理,其實就是在 async 函數內部邏輯映射成了 Generator 函數并集成了一個類似于 co 的執行器,所以我們使用 async/await 的時候,代碼更簡潔,沒有了自己觸發遍歷器的 next 或調用 co 充當執行器的過程,只需要關心 async 函數的內部邏輯就可以了,因為寫法與同步相同,更提高了代碼的可讀性,所以說 async/await 是異步編程的終極大招。
由于 async/await 是 ES7 規范,在瀏覽器端的支持并不是那么的友好,所以現在這種寫法多用在 NodeJS 的異步操作當中,在 NodeJS 框架 Koa 2.x 版本得到廣泛應用。
最后希望大家在讀過異步發展流程這個系列之后,對 JavaScript 異步已經有了較深的認識,并可以在不同情況下游刃有余的使用這些處理異步的編程手段。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98279.html
摘要:所以異步編程對語言太重要。異步編程我們就以用戶注冊這個特別常見的場景為例,講講異步編程。這種層層嵌套被稱為回調地獄。相比回調函數而言,代碼可讀性更高,代碼的執行順序一目了然。函數內部語句返回的值,會成為方法回調函數的參數。 單線程是Javascript語言最本質的特性之一,Javascript引擎在運行js代碼的時候,同一個時間只能執行單個任務。 這種模式的好處是實現起來比較簡單,執行...
摘要:異步流程管理說白了就是為了解決回調地獄的問題。對象代表一個異步操作,有三種狀態進行中已成功和已失敗。如果改變已經發生了,你再對對象添加回調函數,也會立即得到這個結果。執行函數后返回的是一個遍歷器對象,可以依次遍歷函數內部的每一個狀態。 javascript -- 深度解析異步解決方案 高級語言層出不窮, 然而唯 js 鶴立雞群, 這要說道js的設計理念, js天生為異步而生, 正如布道...
摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:感謝大神的免費的計算機編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學習簡介現在,越來越多的科技公司和開發者開始使用開發各種應用。 說明 2017-12-14 我發了一篇文章《沒用過Node.js,就別瞎逼逼》是因為有人在知乎上黑Node.js。那篇文章的反響還是相當不錯的,甚至連著名的hax賀老都很認同,下班時讀那篇文章,竟然坐車的還坐過站了。大家可以很...
摘要:感謝大神的免費的計算機編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學習簡介現在,越來越多的科技公司和開發者開始使用開發各種應用。 說明 2017-12-14 我發了一篇文章《沒用過Node.js,就別瞎逼逼》是因為有人在知乎上黑Node.js。那篇文章的反響還是相當不錯的,甚至連著名的hax賀老都很認同,下班時讀那篇文章,竟然坐車的還坐過站了。大家可以很...
閱讀 2722·2021-11-22 13:54
閱讀 1063·2021-10-14 09:48
閱讀 2292·2021-09-08 09:35
閱讀 1550·2019-08-30 15:53
閱讀 1166·2019-08-30 13:14
閱讀 606·2019-08-30 13:09
閱讀 2521·2019-08-30 10:57
閱讀 3334·2019-08-29 13:18