編寫js async函數步驟詳解
這次帶給大家寫js async函數步驟詳解,寫js async函數的注意事項有哪些,以下就是實戰案例,一起來看一下。
2018年已經到了5月份,node的4.x版本也已經停止了維護我司的某個服務也已經切到了8.x,目前正在做koa2.x的遷移將之前的generator全部替換為async但是,在替換的過程中,發現一些濫用async導致的時間上的浪費所以來談一下,如何優化async代碼,更充分的利用異步事件流杜絕濫用async
#首先,你需要了解Promise
Promise是使用async/await的基礎,所以你一定要先了解Promise是做什麼的
Promise是幫助解決回調地獄的一個好東西,能夠讓非同步流程變得更清晰。
一個簡單的Error-first-callback轉換為Promise的例子:
const fs = require('fs') function readFile (fileName) { return new Promise((resolve, reject) => { fs.readFile(fileName, (err, data) => { if (err) reject(err) resolve(data) }) }) } readFile('test.log').then(data => { console.log('get data') }, err => { console.error(err) })
我們呼叫函數傳回一個Promise的實例,在實例化的過程中進行檔案的讀取,當文件讀取的回呼觸發式,進行Promise狀態的變更,resolved或rejected狀態的變更我們使用then來監聽,第一個回調至resolve的處理,第二個回調為reject的處理。
async與Promise的關係
async函數相當於一個簡寫的傳回Promise實例的函數,效果如下:
function getNumber () { return new Promise((resolve, reject) => { resolve(1) }) } // => async function getNumber () { return 1 }
兩者在使用上方式上完全一樣,都可以在呼叫getNumber函數後使用then進行監聽回傳值。以及與async對應的await語法的使用方式:
getNumber().then(data => { // got data }) // => let data = await getNumber()
await的執行會取得表達式後邊的Promise執行結果,相當於我們呼叫then取得回呼結果一樣。 P.S. 在async/await支持度還不是很高的時候,大家都會選擇使用generator/yield結合著一些類似於co的庫來實現類似的效果
async函數代碼執行是同步的,結果返回是異步的
async函數總是會傳回一個Promise的實例這點兒很重要所以說呼叫一個async函數時,可以理解為裡邊的程式碼都是處於new Promise中,所以是同步執行的而最後return的操作,則相當於在Promise中調用resolve:
async function getNumber () { console.log('call getNumber()') return 1 } getNumber().then(_ => console.log('resolved')) console.log('done') // 输出顺序: // call getNumber() // done // resolved
#Promise內部的Promise會被消化
也就是說,如果我們有如下的程式碼:
function getNumber () { return new Promise(resolve => { resolve(Promise.resolve(1)) }) } getNumber().then(data => console.log(data)) // 1
如果按照上邊說的話,我們在then裡邊取得到的data應該是傳入resolve中的值,也就是另一個Promise的實例。
但實際上,我們會直接獲得回傳值:1,也就是說,如果在Promise中回傳一個Promise,實際上程式會幫我們執行這個Promise,並在內部的Promise狀態改變時觸發then之類的回調。
一個有意思的事情:
function getNumber () { return new Promise(resolve => { resolve(Promise.reject(new Error('Test'))) }) } getNumber().catch(err => console.error(err)) // Error: Test
如果我們在resolve中傳入了一個reject,則我們在外部則可以直接使用catch監聽到。
這種方式經常用於在async函數中拋出異常 如何在async函數中拋出異常:
async function getNumber () { return Promise.reject(new Error('Test')) } try { let number = await getNumber() } catch (e) { console.error(e) }
一定不要忘了await關鍵字
如果忘記加入await關鍵字,程式碼層面並不會報錯,但是我們接收到的回傳值卻是一個Promise
let number = getNumber() console.log(number) // Promise
所以在使用時一定要切記await關鍵字
let number = await getNumber() console.log(number) // 1
不是所有的地方都需要添加await
在程式碼的執行過程中,有時候,並不是所有的非同步都要添加await的。例如下邊的對文件的操作:
我們假設fs所有的API都被我們轉換為了Promise版本
let number = await getNumber() console.log(number) // 1
我們透過await開啟一個文件,然後進行兩次文件的寫入。
但是注意了,在兩次檔案的寫入操作前邊,我們並沒有加入await關鍵字。
因為這是多餘的,我們只需要通知API,我要往這個文件裡邊寫入一行文本,順序自然會由fs來控制
然後我們在最後使用await來關閉這個文件。
因為如果我們上邊在執行寫入的過程還沒完成時,close的回呼是不會觸發的,
也就是說,回呼的觸發就意味著上邊兩步的write已經執行完成了。
合并多个不相干的async函数调用
如果我们现在要获取一个用户的头像和用户的详细信息(而这是两个接口 虽说一般情况下不太会出现)
async function getUser () { let avatar = await getAvatar() let userInfo = await getUserInfo() return { avatar, userInfo } }
这样的代码就造成了一个问题,我们获取用户信息的接口并不依赖于头像接口的返回值。
但是这样的代码却会在获取到头像以后才会去发送获取用户信息的请求。
所以我们对这种代码可以这样处理:
async function getUser () { let [avatar, userInfo] = await Promise.all([getAvatar(), getUserInfo()]) return { avatar, userInfo } }
这样的修改就会让getAvatar与getUserInfo内部的代码同时执行,同时发送两个请求,在外层通过包一层Promise.all来确保两者都返回结果。
让相互没有依赖关系的异步函数同时执行
一些循环中的注意事项
forEach
当我们调用这样的代码时:
async function getUsersInfo () { [1, 2, 3].forEach(async uid => { console.log(await getUserInfo(uid)) }) } function getuserInfo (uid) { return new Promise(resolve => { setTimeout(_ => resolve(uid), 1000) }) } await getUsersInfo()
这样的执行好像并没有什么问题,我们也会得到1、2、3三条log的输出,但是当我们在await getUsersInfo()下边再添加一条console.log('done')的话,就会发现:
我们会先得到done,然后才是三条uid的log,也就是说,getUsersInfo返回结果时,其实内部Promise并没有执行完。
这是因为forEach并不会关心回调函数的返回值是什么,它只是运行回调。
不要在普通的for、while循环中使用await
使用普通的for、while循环会导致程序变为串行:
for (let uid of [1, 2, 3]) { let result = await getUserInfo(uid) }
这样的代码运行,会在拿到uid: 1的数据后才会去请求uid: 2的数据
--------------------------------------------------------------------------------
关于这两种问题的解决方案:
目前最优的就是将其替换为map结合着Promise.all来实现:
await Promise.all([1, 2, 3].map(async uid => await getUserInfo(uid)))
这样的代码实现会同时实例化三个Promise,并请求getUserInfo
P.S. 草案中有一个await*,可以省去Promise.all
await Promise.all([1, 2, 3].map(async uid => await getUserInfo(uid)))
P.S. 为什么在使用Generator+co时没有这个问题
在使用koa1.x的时候,我们直接写yield [].map是不会出现上述所说的串行问题的看过co源码的小伙伴应该都明白,里边有这么两个函数(删除了其余不相关的代码):
function toPromise(obj) { if (Array.isArray(obj)) return arrayToPromise.call(this, obj); return obj; } function arrayToPromise(obj) { return Promise.all(obj.map(toPromise, this)); }
co是帮助我们添加了Promise.all的处理的(膜拜TJ大佬)。
总结
总结一下关于async函数编写的几个小提示:
1.使用return Promise.reject()在async函数中抛出异常
2.让相互之间没有依赖关系的异步函数同时执行
3.不要在循环的回调中/for、while循环中使用await,用map来代替它
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
以上是編寫js async函數步驟詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Windows作業系統是全球最受歡迎的作業系統之一,其新版本Win11備受矚目。在Win11系統中,管理員權限的取得是一個重要的操作,管理員權限可以讓使用者對系統進行更多的操作和設定。本文將詳細介紹在Win11系統中如何取得管理員權限,以及如何有效地管理權限。在Win11系統中,管理員權限分為本機管理員和網域管理員兩種。本機管理員是指具有對本機電腦的完全管理權限

OracleSQL中的除法運算詳解在OracleSQL中,除法運算是一種常見且重要的數學運算運算,用來計算兩個數相除的結果。除法在資料庫查詢中經常用到,因此了解OracleSQL中的除法運算及其用法是資料庫開發人員必備的技能之一。本文將詳細討論OracleSQL中除法運算的相關知識,並提供具體的程式碼範例供讀者參考。一、OracleSQL中的除法運算

Linux系統呼叫system()函數詳解系統呼叫是Linux作業系統中非常重要的一部分,它提供了一種與系統核心互動的方式。其中,system()函數是常用的系統呼叫函數之一。本文將詳細介紹system()函數的使用方法,並提供對應的程式碼範例。系統呼叫的基本概念系統呼叫是使用者程式與作業系統核心互動的一種方式。使用者程式透過呼叫系統呼叫函數來請求作業系統

PHP中的模運算子(%)是用來取得兩個數值相除的餘數的。在本文中,我們將詳細討論模運算子的作用及用法,並提供具體的程式碼範例來幫助讀者更好地理解。 1.模運算子的作用在數學中,當我們將一個整數除以另一個整數時,就會得到一個商和一個餘數。例如,當我們將10除以3時,商數為3,餘數為1。模運算子就是用來取得這個餘數的。 2.模運算子的用法在PHP中,使用%符號來表示模

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

Linux的curl命令詳解摘要:curl是一種強大的命令列工具,用於與伺服器進行資料通訊。本文將介紹curl指令的基本用法,並提供實際的程式碼範例,幫助讀者更好地理解和應用該指令。一、curl是什麼? curl是命令列工具,用於發送和接收各種網路請求。它支援多種協議,如HTTP、FTP、TELNET等,並提供了豐富的功能,如檔案上傳、檔案下載、資料傳輸、代

Promise.resolve()詳解,需要具體程式碼範例Promise是JavaScript中一種用來處理非同步操作的機制。在實際開發中,常常需要處理一些需要依序執行的非同步任務,而Promise.resolve()方法就是用來傳回一個已經Fulfilled狀態的Promise物件。 Promise.resolve()是Promise類別的靜態方法,它接受一個

Numpy是一款Python科學計算庫,提供了豐富的陣列操作函數與工具。升級Numpy版本時需要查詢目前版本以確保相容性,本文將詳細介紹Numpy版本查詢的方法,並提供具體的程式碼範例。方法一:使用Python程式碼查詢Numpy版本使用Python程式碼可以輕鬆查詢Numpy的版本,以下是實作方法和範例程式碼:importnumpyasnpprint(np
