今回は、js async 関数を最適化する方法と、js 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) })
async と Promise の関係
async 関数は、Promise インスタンスを返す短縮関数と同等であり、その効果は次のとおりです:function getNumber () { return new Promise((resolve, reject) => { resolve(1) }) } // => async function getNumber () { return 1 }
getNumber().then(data => { // got data }) // => let data = await getNumber()
async 関数常に Promise のインスタンスを返します。これは非常に重要です。そのため、async 関数を呼び出すと、内部のコードが新しい Promise にあることがわかり、同期的に実行され、最後の return 操作は、resolve を呼び出した場合と同じになります。 Promise:
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
しかし実際には、戻り値を直接取得します。 1. つまり、Promise が Promise で返された場合、プログラムは実際にこの Promise の実行を支援し、内部 Promise の状態が変化したときなどにコールバックをトリガーします。 。
興味深い点:
function getNumber () { return new Promise(resolve => { resolve(Promise.reject(new Error('Test'))) }) } getNumber().catch(err => console.error(err)) // Error: Test
このメソッドは、非同期関数で例外をスローするためによく使用されます
非同期関数で例外をスローする方法:async function getNumber () {
return Promise.reject(new Error('Test'))
}
try {
let number = await getNumber()
} catch (e) {
console.error(e)
}
await キーワードを忘れないように注意してください await キーワードを追加するのを忘れた場合は、コードレベル Noエラーは報告されますが、受け取る戻り値は 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 を通じてファイルを開き、ファイルを 2 回書き込みます。
ただし、2 つのファイル書き込み操作の前に await キーワードを追加していないことに注意してください。
これは冗長であるため、このファイルにテキスト行を書き込みたいことを API に通知するだけで済みます。順序は当然 fs によって制御されます。 その後、最後に await を使用してファイルを閉じます。
上記の書き込み処理を実行しても、close コールバックはトリガーされないためです。
つまり、コールバックのトリガーは、上記の 2 つの書き込みステップが完了したことを意味します。
無関係な複数の非同期関数呼び出しをマージしますユーザーのアバターとユーザーの詳細を取得したい場合 (これらは 2 つのインターフェースですが、通常の状況では表示される可能性は低いです)
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の非同期関数を最適化する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。