blog

ジェネレーターを使って非同期awaitを書く

asyncのawaitはES7の非同期ソリューションで、非同期処理関数を同期コードで書けるようにするもので、実際、内部はES6のPromiseとジェネレータの賢い組み合わせです。 どのような効果を達成...

Feb 25, 2020 · 3 min. read
Share this

この記事を読む前に、予備知識を準備しておく必要がある: ES6ジェネレーター

async awaitはES7の非同期ソリューションで、非同期ハンドラを同期コードで記述できるようにするものです。

ジェネレータ関数

  • ジェネレーター関数は、*とyieldを持つ通常の関数です。

ジェネレーター関数は以下に正しく書かれています:

// 関数名に頼る
function *generator(){
	yiled 'cellphone'
}
// 関数宣言に頼る
function* generator(){
	yield 'cellphone'
}
// どちらにも頼れない。
function * generator(){
	yield 'cellphone'
}
  • ジェネレータ関数
// 非同期をシミュレートするために2つの "Promises "を宣言する
const getData = Promise.resolve(1)
const login = Promise.resolve(2)
// 以下のコードを書くことができれば、次のような効果を得ることができる。,
// これはasync awaitの実装に相当する。
function *fn() {
 const a = yield getData;
 console.log(a); // 1
 const b = yield login;
 console.log(b); // 2
}
// 重要なのは、ジェネレーター関数をどう扱うかだ。,
// 上記のコードの結果を得るには、下を見てほしい!
// 1.fn通常の関数のように実行されるが、実際には実行されない。,
//イテレータジェネレーターを手に入れる,gen.next()関数の実行を制御する
const gen = fn();
/*nextが初めて呼ばれると、関数は実行を開始し、最初のyoildに遭遇する。,
関数はあるフィールドで停止し、そのフィールドの結果を返す。,
yiledオブジェクト {valueGetData, done: false} 
valueこれはyieldの結果で、doneは関数の実行が終わったかどうかを表す。
ここでのgetaDataはreturnの結果である。*/
const p1 = gen.next().value
p1.then(e => {
	// 回目のnext(e + 100) e + 100 
 // 最初のyieldの結果を待つ。 = e + 100
 const p2 = gen.next(e + 100).value
 p2.then(r => {
 	// ここでもそうだ。 = r + 200
 const p3 = gen.next(r + 200)
 console.log(p3);
 })
})

async関数の実装

  • どのような効果が得られるかを確認するための実装は、おなじみのasync awaitの書き方に加え、代わりに*を付けたasync、代わりにyieldを付けたawait、その他は単純に同じです。
// ES7 async await 
const getData = Promise.resolve(1)
const login = Promise.resolve(2)
function async fn() {
 const a = await getData;
 console.log(a);
 const b = await login;
 console.log(b);
 return 'someValue'
}
// ジェネレーターを使って書くには、結果を処理する必要がある。 
// これはmyAsync関数実装のコアロジックの次のステップだ。
// つまり、ネイティブのasync await実装の原理だ。
const getData = Promise.resolve(1)
const login = Promise.resolve(2)
function* fn() {
 const a = yield getData;
 console.log(a); // 1
 const b = yield login;
 console.log(b); // 2
 return 'someValue'
}
/**
 * コードを一行ずつ説明する
 * @param {ジェネレーター関数} generator
 */
function myAsync(generator) {
 // パラメータを渡す必要がある場合のリターン関数
 return function () {
 // ジェネレーター関数を実行する。
 const gen = generator.apply(this, arguments)
 if (!gen || !gen.next) throw new Error('function myAsync 引数はジェネレーター関数ではない')
 // Promiseジェネレーターの役割は、関数の戻り値を処理することだ。
 return new Promise( resolve => {
 // フィールドの出現を再帰的に返す関数
 function step(gen, result) {
 // 前の実行結果を返し、次のyieldを得る。
 const rv = gen.next(result)
 // 再帰的終了は、resolveを使って最後のreturnの結果を返す。
 if (rv.done) {
 resolve(rv.value)
 return
 }
 // yiledの値を取得する
 const p = rv.value
 // 再帰のためのPromiseなのか通常の値なのかを判断する
 p instanceof Promise ? p.then(r => {
 step(gen, r)
 }) : step(gen, p)
 }
 step(gen)
 })
 }
}
 //  
 const result = myAsync(fn)

まとめ

上のコードはコアコードを実装したもので、多くのケースは考慮されていませんし、エラー処理もありませんが、ジェネレータがどのように動作し、asyncとawaitを実装するためにどのように巧妙に借用されているかを見るには良い方法です。

Read next

リーコード|常連ダブルポインターアルゴリズム問題

leetcode|クラシカルパワーバックル第1問 この3つの数の和の問題をシェアします。ちょっと難しいかもしれませんので、賢い頭を使って、一緒に粘っていただければと思います! 今日の笑い 私より優秀な人は努力しないのに、どうして私より優秀なの? 説明 n個の

Feb 24, 2020 · 3 min read