async function_掌握 Async/Await
摘要: 還不用Async/Await就OUT了。。
- 原文:掌握 Async/Await
- 作者:Jartto
Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。
前端工程師肯定都經(jīng)歷過 JS 回調(diào)鏈獄的痛苦過程,我們在使用 Promise 的時(shí)候總是不盡人意。這時(shí)候 Async/Await 應(yīng)運(yùn)而生,它到底有什么魔力,我們來說道說道。
一、回顧 Promise
所謂 Promise,簡單說就是一個(gè)容器,里面保存著某個(gè)未來才會結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果。
1. 語法
new Promise(executor); new Promise(function(resolve, reject) { ... });2. 參數(shù)
帶有 resolve 、reject 兩個(gè)參數(shù)的一個(gè)函數(shù)。這個(gè)函數(shù)在創(chuàng)建 Promise 對象的時(shí)候會立即得到執(zhí)行(在 Promise 構(gòu)造函數(shù)返回 Promise 對象之前就會被執(zhí)行),并把成功回調(diào)函數(shù)(resolve)和失敗回調(diào)函數(shù)(reject)作為參數(shù)傳遞進(jìn)來。調(diào)用成功回調(diào)函數(shù)(resolve)和失敗回調(diào)函數(shù)(reject)會分別觸發(fā) Promise 的成功或失敗。
這個(gè)函數(shù)通常被用來執(zhí)行一些異步操作,操作完成以后可以選擇調(diào)用成功回調(diào)函數(shù)(resolve)來觸發(fā) Promise 的成功狀態(tài),或者,在出現(xiàn)錯(cuò)誤的時(shí)候調(diào)用失敗回調(diào)函數(shù)(reject)來觸發(fā) Promise 的失敗。
3. Promise.all
用 Promise.all 來執(zhí)行,all 接收一個(gè)數(shù)組參數(shù),里面的值最終都算返回 Promise 對象。這樣,三個(gè)異步操作的并行執(zhí)行的,等到它們都執(zhí)行完后才會進(jìn)到 then 里面。
Promise.all([async1(), async2(), async3()]) .then(function(results){console.log(results); });all 會把所有異步操作的結(jié)果放進(jìn)一個(gè)數(shù)組中傳給 then,就是上面的 results。
4. Promise.race
all 方法的效果實(shí)際上是「誰跑的慢,以誰為準(zhǔn)執(zhí)行回調(diào)」,那么相對的就有另一個(gè)方法「誰跑的快,以誰為準(zhǔn)執(zhí)行回調(diào)」,這就是 race方法:
Promise.race([requestImg(), timeout()]) .then(function(results){console.log(results); }) .catch(function(reason){console.log(reason); });上述代碼演示了 race 的基本用法,實(shí)現(xiàn)的功能是:請求圖片,如果請求成功就返回圖片,否則就調(diào)用超時(shí)函數(shù)。
更多資源,請查看:
- js promise
- 白話 promise
- promise 對象-阮一峰
- promise
二、Promise 為何不完美?
乍一看,Promise 還不錯(cuò),幫我們解決了回調(diào)鏈獄的問題。當(dāng)然這只是簡單使用,碰到復(fù)雜的業(yè)務(wù)也有很雞肋的場景,比如:
1. 錯(cuò)誤處理
在下面的 Promise 示例中,Try/Catch 不能處理 JSON.parse 的錯(cuò)誤,因?yàn)樗?Promise中。我們需要使用 catch,這樣錯(cuò)誤處理代碼非常冗余。并且,在我們的實(shí)際生產(chǎn)代碼會更加復(fù)雜。
const makeRequest = () => {try {getJSON().then(result => {// JSON.parse可能會出錯(cuò)const data = JSON.parse(result)console.log(data)})// 取消注釋,處理異步代碼的錯(cuò)誤// .catch((err) => {// console.log(err)// })} catch (err) {console.log(err)} }Async/Await 讓 Try/Catch 可以同時(shí)處理同步和異步錯(cuò)誤。使用 Async/Await 的話,Catch能處理 JSON.parse 錯(cuò)誤:
const makeRequest = async () => {try {// this parse may failconst data = JSON.parse(await getJSON())console.log(data)} catch (err) {console.log(err)} }Async/Await 最讓人舒服的一點(diǎn)是代碼看起來是同步的。
2. 條件語句
下面示例中,需要獲取數(shù)據(jù),然后根據(jù)返回?cái)?shù)據(jù)決定是直接返回,還是繼續(xù)獲取更多的數(shù)據(jù)。
const makeRequest = () => {return getJSON().then(data => {if (data.needsAnotherRequest) {return makeAnotherRequest(data).then(moreData => {console.log(moreData)return moreData})} else {console.log(data)return data}}) }這些代碼看著就頭痛。嵌套(6層),括號,return 語句很容易讓人感到迷茫,而它們只是需要將最終結(jié)果傳遞到最外層的Promise。如果換成 Async/Await 呢:
const makeRequest = async () => {const data = await getJSON();if (data.needsAnotherRequest) {const moreData = await makeAnotherRequest(data);console.log(moreData);return moreData;} else {console.log(data);return data; } }所以,這才是真正擺脫回調(diào)鏈獄的正確做法。
3. 中間值
你很可能遇到過這樣的場景,調(diào)用 promise1,使用 promise1 返回的結(jié)果去調(diào)用 promise2,然后使用兩者的結(jié)果去調(diào)用promise3。你的代碼很可能是這樣的:
const makeRequest = () => {return promise1().then(value1 => {return promise2(value1);.then(value2 => { return promise3(value1, value2);})}) } // 或者: const makeRequest = () => {return promise1().then(value1 => {return Promise.all([value1, promise2(value1)])}).then(([value1, value2]) => { return promise3(value1, value2)}) }怎么寫都會覺得很復(fù)雜,那如果 Async/Await 用來實(shí)現(xiàn)呢,表現(xiàn)可能如下:
const makeRequest = async () => {const value1 = await promise1();const value2 = await promise2(value1);return promise3(value1, value2); }是不是很 6 ,將復(fù)雜的場景簡化,這樣的代碼就很有靈性了。
4. 錯(cuò)誤棧
調(diào)用了多個(gè) Promise,假設(shè) Promise 鏈中某個(gè)地方拋出了一個(gè)錯(cuò)誤,Promise 鏈中返回的錯(cuò)誤棧沒有給出錯(cuò)誤發(fā)生位置的線索。更糟糕的是,它會誤導(dǎo)我們;錯(cuò)誤棧中唯一的函數(shù)名為 callAPromise,然而它和錯(cuò)誤沒有關(guān)系。(文件名和行號還是有用的)。
const makeRequest = () => {return callAPromise().then(() => callAPromise()).then(() => callAPromise()).then(() => callAPromise()).then(() => callAPromise()).then(() => {throw new Error("oops");}) }makeRequest().catch(err => {console.log(err);// output// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13) })然而,Async/Await 中的錯(cuò)誤棧會指向錯(cuò)誤所在的函數(shù):
const makeRequest = async () => {await callAPromise();await callAPromise();await callAPromise();throw new Error("oops"); }makeRequest().catch(err => {console.log(err);// output// Error: oops at makeRequest (index.js:7:9) })5. 調(diào)試
調(diào)試 Promise 有兩個(gè)問題:
- 不能在返回表達(dá)式的箭頭函數(shù)中設(shè)置斷點(diǎn);
- 如果你在 then 代碼塊中設(shè)置斷點(diǎn),調(diào)試器不會跳到下一個(gè) then,因?yàn)樗粫^異步代碼;
而使用 Await/Async 時(shí),你不再需要那么多箭頭函數(shù),這樣你就可以像調(diào)試同步代碼一樣跳過 Await 語句。
這里只簡單的列出問題,詳細(xì)請查看原文:Async/Await 替代 Promise 的 6 個(gè)理由
三、新時(shí)代的曙光 Async/Await
簡單介紹:
- Await/Async 是寫異步代碼的新方式,以前的方法有回調(diào)函數(shù)和 Promise。
- Await/Async 是基于 Promise 實(shí)現(xiàn)的,它不能用于普通的回調(diào)函數(shù)。
- Await/Async 與 Promise 一樣,是非阻塞的。
- Await/Async 使得異步代碼看起來像同步代碼,這正是它的魔力所在。
使用 Promise 是這樣的:
const jarttoDemo = () => getJSON().then(data => {return data; })jarttoDemo();使用 Async/Await 是這樣的:
const jarttoDemo = async () => {let data = await getJSON();return data; }jarttoDemo();基本規(guī)則:
- Async 表示這是一個(gè) Async 函數(shù),Await 只能用在這個(gè)函數(shù)里面。
- Await 表示在這里等待 Promise返回結(jié)果了,再繼續(xù)執(zhí)行。
- Await 后面跟著的應(yīng)該是一個(gè) Promise 對象,當(dāng)然,其他返回值也沒關(guān)系,只是會立即執(zhí)行,不過那樣就沒有意義了。
四、更多用法示例
1. 簡單示例
var sleep = function (time) {return new Promise(function (resolve, reject) {setTimeout(function () {resolve();}, time);}) };var start = async function () {// 在這里使用起來就像同步代碼那樣直觀console.log('start');await sleep(3000);console.log('end'); };start();2. 獲得返回值
var sleep = function (time) {return new Promise(function (resolve, reject) {setTimeout(function () {// 返回 ‘ok’resolve('ok');}, time);}) };var start = async function () {let result = await sleep(3000);console.log(result); // 收到 ‘ok’ };3. 錯(cuò)誤捕獲
const makeRequest = async () => {try {// this parse may failconst data = JSON.parse(await getJSON())console.log(data)} catch (err) {console.log(err)} }既然 then 不用寫了,那么 catch 也不用寫,可以直接用標(biāo)準(zhǔn)的 try catch 語法捕捉錯(cuò)誤。
var sleep = function (time) {return new Promise(function (resolve, reject) {setTimeout(function () {// 模擬出錯(cuò)了,返回 ‘error’reject('error');}, time);}) };var start = async function () {try {console.log('start');await sleep(3000); // 這里得到了一個(gè)返回錯(cuò)誤// 所以以下代碼不會被執(zhí)行了console.log('end');} catch (err) {console.log(err); // 這里捕捉到錯(cuò)誤 `error`} };4. 條件語句
Promise 寫法:
const makeRequest = () => {return getJSON().then(data => {if (data.needsAnotherRequest) {return makeAnotherRequest(data).then(moreData => {return moreData;})} else {return data;}}) }Async/Await 寫法:
const makeRequest = async () => {const data = await getJSON();if (data.needsAnotherRequest) {const moreData = await makeAnotherRequest(data);return moreData;} else {return data; } }5. 循環(huán)多個(gè) Await
var start = async function () {for (let i = 1; i <= 10; i++) {console.log(`當(dāng)前是第 ${i} 次等待..`);await sleep(1000);} };需要注意的是,Await 必須在 Async 函數(shù)的上下文中的。
6. 在 forEach 中使用
async function printFiles() {const files = await getFilePaths();for (let file of files) {const contents = await fs.readFile(file, "utf8");console.log(contents);} }async function printFiles() {const files = await getFilePaths();await Promise.all(files.map(file => {return async () => {const contents = await fs.readFile(file, "utf8");console.log(contents);};})); }示例參考如下文章:
- Async/Await替代Promise的6個(gè)理由
- ES7 的 Async/Await
- Using async/await with a forEach loop
五、總結(jié)
我們一直在強(qiáng)調(diào)代碼的可讀性和可維護(hù)性,對我來說,Async/Await 更加易懂和易用。所以,不管是 Promise 還是 Async/Await ,能解決實(shí)際問題的技術(shù)就是好技術(shù)。
當(dāng)然,Async/Await 也是基于 Promise 概念的,技術(shù)上我們也可以求同存異,不必太過較真。一句話,選擇權(quán)在你!
總結(jié)
以上是生活随笔為你收集整理的async function_掌握 Async/Await的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电子产品设计流程_产品设计“学习、就业、
- 下一篇: 三级网络技术_三级网络技术考前选择题—V