ホームページ >ウェブフロントエンド >jsチュートリアル >jsの非同期関数を最適化する方法

jsの非同期関数を最適化する方法

php中世界最好的语言
php中世界最好的语言オリジナル
2018-05-30 10:16:541551ブラウズ

今回は、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)
})
Promise インスタンスを返す関数を呼び出し、インスタンス化プロセス中にファイルを読み取ります。ファイル読み取りのコールバックがトリガーされると、 then を使用します。 Promise ステータス、解決済みまたは拒否されたステータスの変化を監視します。最初のコールバックは解決処理用であり、2 番目のコールバックは拒否処理用です。

async と Promise の関係

async 関数は、Promise インスタンスを返す短縮関数と同等であり、その効果は次のとおりです:

function getNumber () {
 return new Promise((resolve, reject) => {
  resolve(1)
 })
}
// =>
async function getNumber () {
 return 1
}
この 2 つは使用上まったく同じであり、後で使用できます。 getNumber 関数を呼び出して戻り値を監視します。 そして、async に対応する await 構文の使用方法:

getNumber().then(data => {
 // got data
})
// =>
let data = await getNumber()
await を実行すると、

expression の背後にある Promise 実行結果が取得されます。これは、コールバック結果を取得するために then を呼び出すのと同等です。追伸: async/await のサポートがそれほど高くなかったときは、誰もが同様の効果を達成するために、co に似たいくつかのライブラリとジェネレーター/yield を組み合わせて使用​​することを選択していました

async 関数のコードの実行は同期であり、結果の戻りは非同期です

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
上記の内容に従えば、データは取得されます。別の Promise インスタンスであるsolveに渡される値。

しかし実際には、戻り値を直接取得します。 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
解決で拒否を渡す場合、catch を直接使用して外部からそれを監視できます。

このメソッドは、非同期関数で例外をスローするためによく使用されます
非同期関数で例外をスローする方法:

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中文网其它相关文章!

推荐阅读:

怎样使用垃圾回收器

怎样使用Nodejs内存治理

怎样使用vue的toast弹窗组件

以上がjsの非同期関数を最適化する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。