この記事を読む前に、予備知識を準備しておく必要がある: 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を実装するためにどのように巧妙に借用されているかを見るには良い方法です。