摘要:標準引入了函數,使得異步操作變得更加方便。在異步處理上,函數就是函數的語法糖。在實際項目中,錯誤處理邏輯可能會很復雜,這會導致冗余的代碼。的出現使得就可以捕獲同步和異步的錯誤。如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。
async
ES2017 標準引入了 async 函數,使得異步操作變得更加方便。
在異步處理上,async 函數就是 Generator 函數的語法糖。
舉個例子:
// 使用 generator var fetch = require("node-fetch"); var co = require("co"); function* gen() { var r1 = yield fetch("https://api.github.com/users/github"); var json1 = yield r1.json(); console.log(json1.bio); } co(gen);
當你使用 async 時:
// 使用 async var fetch = require("node-fetch"); var fetchData = async function () { var r1 = await fetch("https://api.github.com/users/github"); var json1 = await r1.json(); console.log(json1.bio); }; fetchData();
其實 async 函數的實現原理,就是將 Generator 函數和自動執行器,包裝在一個函數里。
async function fn(args) { // ... } // 等同于 function fn(args) { return spawn(function* () { // ... }); }
spawn 函數指的是自動執行器,就比如說 co。
再加上 async 函數返回一個 Promise 對象,你也可以理解為 async 函數是基于 Promise 和 Generator 的一層封裝。
async 與 Promise嚴謹的說,async 是一種語法,Promise 是一個內置對象,兩者并不具備可比性,更何況 async 函數也返回一個 Promise 對象……
這里主要是展示一些場景,使用 async 會比使用 Promise 更優雅的處理異步流程。
1. 代碼更加簡潔/** * 示例一 */ function fetch() { return ( fetchData() .then(() => { return "done" }); ) } async function fetch() { await fetchData() return "done" };
/** * 示例二 */ function fetch() { return fetchData() .then(data => { if (data.moreData) { return fetchAnotherData(data) .then(moreData => { return moreData }) } else { return data } }); } async function fetch() { const data = await fetchData() if (data.moreData) { const moreData = await fetchAnotherData(data); return moreData } else { return data } };
/** * 示例三 */ function fetch() { return ( fetchData() .then(value1 => { return fetchMoreData(value1) }) .then(value2 => { return fetchMoreData2(value2) }) ) } async function fetch() { const value1 = await fetchData() const value2 = await fetchMoreData(value1) return fetchMoreData2(value2) };2. 錯誤處理
function fetch() { try { fetchData() .then(result => { const data = JSON.parse(result) }) .catch((err) => { console.log(err) }) } catch (err) { console.log(err) } }
在這段代碼中,try/catch 能捕獲 fetchData() 中的一些 Promise 構造錯誤,但是不能捕獲 JSON.parse 拋出的異常,如果要處理 JSON.parse 拋出的異常,需要添加 catch 函數重復一遍異常處理的邏輯。
在實際項目中,錯誤處理邏輯可能會很復雜,這會導致冗余的代碼。
async function fetch() { try { const data = JSON.parse(await fetchData()) } catch (err) { console.log(err) } };
async/await 的出現使得 try/catch 就可以捕獲同步和異步的錯誤。
3. 調試const fetchData = () => new Promise((resolve) => setTimeout(resolve, 1000, 1)) const fetchMoreData = (value) => new Promise((resolve) => setTimeout(resolve, 1000, value + 1)) const fetchMoreData2 = (value) => new Promise((resolve) => setTimeout(resolve, 1000, value + 2)) function fetch() { return ( fetchData() .then((value1) => { console.log(value1) return fetchMoreData(value1) }) .then(value2 => { return fetchMoreData2(value2) }) ) } const res = fetch(); console.log(res);
因為 then 中的代碼是異步執行,所以當你打斷點的時候,代碼不會順序執行,尤其當你使用 step over 的時候,then 函數會直接進入下一個 then 函數。
const fetchData = () => new Promise((resolve) => setTimeout(resolve, 1000, 1)) const fetchMoreData = () => new Promise((resolve) => setTimeout(resolve, 1000, 2)) const fetchMoreData2 = () => new Promise((resolve) => setTimeout(resolve, 1000, 3)) async function fetch() { const value1 = await fetchData() const value2 = await fetchMoreData(value1) return fetchMoreData2(value2) }; const res = fetch(); console.log(res);
而使用 async 的時候,則可以像調試同步代碼一樣調試。
async 地獄async 地獄主要是指開發者貪圖語法上的簡潔而讓原本可以并行執行的內容變成了順序執行,從而影響了性能,但用地獄形容有點夸張了點……
例子一舉個例子:
(async () => { const getList = await getList(); const getAnotherList = await getAnotherList(); })();
getList() 和 getAnotherList() 其實并沒有依賴關系,但是現在的這種寫法,雖然簡潔,卻導致了 getAnotherList() 只能在 getList() 返回后才會執行,從而導致了多一倍的請求時間。
為了解決這個問題,我們可以改成這樣:
(async () => { const listPromise = getList(); const anotherListPromise = getAnotherList(); await listPromise; await anotherListPromise; })();
也可以使用 Promise.all():
(async () => { Promise.all([getList(), getAnotherList()]).then(...); })();例子二
當然上面這個例子比較簡單,我們再來擴充一下:
(async () => { const listPromise = await getList(); const anotherListPromise = await getAnotherList(); // do something await submit(listData); await submit(anotherListData); })();
因為 await 的特性,整個例子有明顯的先后順序,然而 getList() 和 getAnotherList() 其實并無依賴,submit(listData) 和 submit(anotherListData) 也沒有依賴關系,那么對于這種例子,我們該怎么改寫呢?
基本分為三個步驟:
1. 找出依賴關系
在這里,submit(listData) 需要在 getList() 之后,submit(anotherListData) 需要在 anotherListPromise() 之后。
2. 將互相依賴的語句包裹在 async 函數中
async function handleList() { const listPromise = await getList(); // ... await submit(listData); } async function handleAnotherList() { const anotherListPromise = await getAnotherList() // ... await submit(anotherListData) }
3.并發執行 async 函數
async function handleList() { const listPromise = await getList(); // ... await submit(listData); } async function handleAnotherList() { const anotherListPromise = await getAnotherList() // ... await submit(anotherListData) } // 方法一 (async () => { const handleListPromise = handleList() const handleAnotherListPromise = handleAnotherList() await handleListPromise await handleAnotherListPromise })() // 方法二 (async () => { Promise.all([handleList(), handleAnotherList()]).then() })()繼發與并發
問題:給定一個 URL 數組,如何實現接口的繼發和并發?
async 繼發實現:
// 繼發一 async function loadData() { var res1 = await fetch(url1); var res2 = await fetch(url2); var res3 = await fetch(url3); return "whew all done"; }
// 繼發二 async function loadData(urls) { for (const url of urls) { const response = await fetch(url); console.log(await response.text()); } }
async 并發實現:
// 并發一 async function loadData() { var res = await Promise.all([fetch(url1), fetch(url2), fetch(url3)]); return "whew all done"; }
// 并發二 async function loadData(urls) { // 并發讀取 url const textPromises = urls.map(async url => { const response = await fetch(url); return response.text(); }); // 按次序輸出 for (const textPromise of textPromises) { console.log(await textPromise); } }async 錯誤捕獲
盡管我們可以使用 try catch 捕獲錯誤,但是當我們需要捕獲多個錯誤并做不同的處理時,很快 try catch 就會導致代碼雜亂,就比如:
async function asyncTask(cb) { try { const user = await UserModel.findById(1); if(!user) return cb("No user found"); } catch(e) { return cb("Unexpected error occurred"); } try { const savedTask = await TaskModel({userId: user.id, name: "Demo Task"}); } catch(e) { return cb("Error occurred while saving task"); } if(user.notificationsEnabled) { try { await NotificationService.sendNotification(user.id, "Task Created"); } catch(e) { return cb("Error while sending notification"); } } if(savedTask.assignedUser.id !== user.id) { try { await NotificationService.sendNotification(savedTask.assignedUser.id, "Task was created for you"); } catch(e) { return cb("Error while sending notification"); } } cb(null, savedTask); }
為了簡化這種錯誤的捕獲,我們可以給 await 后的 promise 對象添加 catch 函數,為此我們需要寫一個 helper:
// to.js export default function to(promise) { return promise.then(data => { return [null, data]; }) .catch(err => [err]); }
整個錯誤捕獲的代碼可以簡化為:
import to from "./to.js"; async function asyncTask() { let err, user, savedTask; [err, user] = await to(UserModel.findById(1)); if(!user) throw new CustomerError("No user found"); [err, savedTask] = await to(TaskModel({userId: user.id, name: "Demo Task"})); if(err) throw new CustomError("Error occurred while saving task"); if(user.notificationsEnabled) { const [err] = await to(NotificationService.sendNotification(user.id, "Task Created")); if (err) console.error("Just log the error and continue flow"); } }async 的一些討論 async 會取代 Generator 嗎?
Generator 本來是用作生成器,使用 Generator 處理異步請求只是一個比較 hack 的用法,在異步方面,async 可以取代 Generator,但是 async 和 Generator 兩個語法本身是用來解決不同的問題的。
async 會取代 Promise 嗎?async 函數返回一個 Promise 對象
面對復雜的異步流程,Promise 提供的 all 和 race 會更加好用
Promise 本身是一個對象,所以可以在代碼中任意傳遞
async 的支持率還很低,即使有 Babel,編譯后也要增加 1000 行左右。
參考(譯) 6 個 Async/Await 優于 Promise 的方面
(譯) 如何逃離 async/await 地獄
精讀《async/await 是把雙刃劍》
ECMAScript 6 入門
How to write async await without try-catch blocks in Javascript
ES6 系列ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標簽模板、箭頭函數、Symbol、Set、Map 以及 Promise 的模擬實現、模塊加載方案、異步處理等內容。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/98642.html
摘要:前言這里的泛指之后的新語法這里的完全是指本文會不斷更新這里的使用是指本文會展示很多的使用場景這里的手冊是指你可以參照本文將項目更多的重構為語法此外還要注意這里不一定就是正式進入規范的語法。 前言 這里的 ES6 泛指 ES5 之后的新語法 這里的 完全 是指本文會不斷更新 這里的 使用 是指本文會展示很多 ES6 的使用場景 這里的 手冊 是指你可以參照本文將項目更多的重構為 ES6...
摘要:到這里,我已經發出了一個請求買漢堡,啟動了一次交易。但是做漢堡需要時間,我不能馬上得到這個漢堡,收銀員給我一個收據來代替漢堡。到這里,收據就是一個承諾保證我最后能得到漢堡。 同期異步系列文章推薦談一談javascript異步javascript異步中的回調javascript異步之Promise.all()、Promise.race()、Promise.finally()javascr...
摘要:第二部分源碼解析接下是應用多個第二部分對于一個方法應用了多個,比如會編譯為在第二部分的源碼中,執行了和操作,由此我們也可以發現,如果同一個方法有多個裝飾器,會由內向外執行。有了裝飾器,就可以改寫上面的代碼。 Decorator 裝飾器主要用于: 裝飾類 裝飾方法或屬性 裝飾類 @annotation class MyClass { } function annotation(ta...
前言 Promise 的基本使用可以看阮一峰老師的 《ECMAScript 6 入門》。 我們來聊點其他的。 回調 說起 Promise,我們一般都會從回調或者回調地獄說起,那么使用回調到底會導致哪些不好的地方呢? 1. 回調嵌套 使用回調,我們很有可能會將業務代碼寫成如下這種形式: doA( function(){ doB(); doC( function(){ ...
閱讀 1756·2021-11-25 09:43
閱讀 1792·2021-11-24 10:41
閱讀 3111·2021-09-27 13:36
閱讀 818·2019-08-30 15:53
閱讀 3575·2019-08-30 15:44
閱讀 871·2019-08-30 14:03
閱讀 2581·2019-08-29 16:38
閱讀 1005·2019-08-29 13:23