blog

非同期/待機原理分析

一文の要約:ジェネレータ関数の構文は砂糖です。 jsの非同期問題に対処するための方法です。 関数宣言でキーワードasync場合、関数は非同期関数です。非同期関数はPromiseオブジェクトを返し、関数...

Aug 28, 2020 · 8 min. read
シェア

初期ブック

一言で言えば、async/awaitはGenerator関数のための構文上の糖分です。

async/await

async/awaitはjsの非同期問題を処理する方法です。

キーワード async が関数宣言に含まれている場合、その関数が非同期関数であることを示します。非同期関数はPromiseオブジェクトを返し、関数の実行が終わると、Promiseと同じようにthenメソッドを呼び出して残りの処理を行うことができます。

awaitキーワードはasyncと一緒に使用する必要があります。awaitキーワードが非同期関数呼び出しに置かれた場合、コードは実行がこの時点に達すると一時停止し、非同期関数が完了するまで待ってから処理を続けます。

まずはデモをご覧ください。

DEMO

function promise1() { return new Promise((resolve, reject) => { console.log('promise1 start'); setTimeout(() => { resolve('promise1 callback'); }, 3000) }) } function promise2() { return new Promise((resolve, reject) => { console.log('promise2 start'); setTimeout(() => { return resolve('promise2 callback'); }, 2000) }) } async function asyncDemo() { console.log('function start'); let res1 = await promise1(); console.log('promise1 end', res1); let res2 = await promise2(); console.log('promise2 end', res2); console.log('function end); return 'finish'; } asyncDemo().then(res => { console.log(res) });

コンソールから、promise1とpromise2が戻り値を返す前に、関数全体が行の下の方で同期的に実行され、次の部分は非同期関数が実行を終えるまでブロックされ、行の下の方で続行されることがわかります。

async/await Reflections

上記のデモを通して、async/await全体には2つの特徴があることがわかります。

1 の場合、返り値は Promise オブジェクトです。

2、awaitはコードの実行をブロックし、非同期実行が完了した後、行を続行します。

1点目については、関数全体をPromiseオブジェクトでラップすることで実装が容易になり、戻り値がPromiseオブジェクトであることが保証されます。2つ目のポイントは、waitの位置でプログラムの実行をブロックする必要があり、waitの後に非同期関数が続く場合は、非同期関数の実行終了を待って、その後のコードの実行を続ける必要があるということです。 ここでは Generator 関数を使用します。

ジェネレーター機能とは何ですか?

Generator関数は、実はES6が提供する非同期プログラミング・ソリューションです。

Generator関数は、いくつかの方法で理解することができます。構文的には、Generator関数はまず、いくつかの内部状態をカプセル化したステートマシンとして理解することができます。Generator関数を実行すると、トラバーサーオブジェクトが返されます。これは、Generator関数がステートマシンであることに加えて、トラバーサーオブジェクトの生成器でもあることを意味します。返されたトラバーサーオブジェクトは、Generator関数の内部状態を順番にトラバースするために使用することができます。

個人的な意見ですが、Generatorは関数内のコードを一時停止/実行する方法を提供してくれます。

ジェネレーター機能には2つの特徴があります。

1.関数キーワードと関数名の間に *; があります。

2.状態制御のための内部使用収穫。

3、nextを使用して、yieldキーワードまたはreturnキーワードに遭遇するまで次の操作を実行します。

例の簡単な説明

function* bar() { let a = yield 'hello'; let obj = yield 'world'; console.log(a + '\x20' + obj); } let c = bar(); console.log(c.next()); console.log(c.next()); console.log(c.next());

上記の例はどのように解釈すべきでしょうか? generatorDemo()を呼び出しても関数は実行されず、内部状態へのポインタ・オブジェクトが返されます。ポインタを次の状態を指すようにするには、後で next メソッドを使用する必要があります。

次のメソッドがオブジェクトを返すたびに

{ value: // yield式の戻り値が続く, done: // ポインタが関数の終わりを指しているかどうか、つまり関数の実行が終了したかどうか。 },

valueはフィールドに続く式が返す値を表し、フィールドに出会わなければそのまま行を進み、returnに出会えばreturnに続く式の値をvalueの値とします。 returnがなければ、valueの値は未定義です。

doneは、ポインタがすべてのyieldオブジェクトを通過し、returnまたは関数の終わりに遭遇したかどうかを示します。

function promise1() { return new Promise((resolve, reject) => { console.log('promise1 start'); setTimeout(() => { resolve('promise1 callback'); }, 3000) }) } function promise2() { return new Promise((resolve, reject) => { console.log('promise2 start'); setTimeout(() => { return resolve('promise2 callback'); }, 2000) }) } async function asyncDemo() { console.log('function start'); let res1 = await promise1(); console.log('promise1 end', res1); let res2 = await promise2(); console.log('promise2 end', res2); console.log('function end); return 'finish'; } asyncDemo().then(res => { console.log(res) }); ここで1つ注意しなければならないのは、上記のコードを実行すると、undefined undefinedと出力されることです。 これは、yield自体に戻り値がないか、戻り値が未定義であるためです。 前のステップのyieldの戻り値が必要な場合は、次のようにする必要があります。

function* a() { let c = yield 'hello'; let bar = yield 'world'; console.log(c + '\x20' + bar); } let b = a(); let foo = b.next(); let obj = b.next(foo.value); b.next(obj.value);

これは、次のメソッドがパラメータを取ることができ、そのパラメータが前の降伏の戻り値として使用され、次の操作に代入されるためです。

次のメソッドで任意のデータを渡すことは問題ありません。

async/awaitは一度呼び出されたら自分で実行できるのに対して、Generator関数は次のステップを手動で実行する必要があるときに自動的に実行するようにするにはどうすればいいのかと質問されるかもしれません。 心配しないでください。

Generator関数が自動的に実行されるためには、Thunk関数のアイデアが必要です。

Thunk

JavaScript において Thunk 関数とは、複数の引数を持つ関数を、コールバック関数のみを引数として受け取る単一の引数を持つ関数に置き換える関数です。

例をあげる

readFile(fileName, callback); function bar(foo) { return function (c) { return readFile(foo, c); }; } let obj = bar('a.text'); obj(callback);

元のreadFileは、fileNameファイル名とコールバック関数の2つのパラメータを受け取る必要があります。 これをthunk関数でラップすることで、コールバック関数を受け取るだけで済みます。 もちろん、この例はちょっとした付け足しのように見えますが、これはthunkのアイデアを説明するためのもので、ジェネレータ関数を自己実行できるようにラップするために使われます。

function* bar() { let obj = yield 'hello'; let a = yield 'world'; console.log(obj + '\x20' + a); } function foo(b) { const c = b(); function C(Foo) { let A = c.next(Foo); if (A.done) { return A.value; } C(A.value); } C(undefined); } foo(bar);

ここでは、genThunk の内部で next 関数を定義しています。各返り値の done 属性を用いて関数の実行が終了したかどうかを判定し、実行が終了していれば得られた値を戻り値として返します。 そうでない場合は、得られた値を前の実行結果として次の関数に渡し、次の実行を続けます。

これを見て、Generator関数とThunk関数について簡単に理解していただけたと思います。

Generator関数によるasync/awaitの実装

次にasync/awaitを実装してみましょう。

これがデモ

function promise1() { return new Promise((resolve, reject) => { console.log('promise1 start'); setTimeout(() => { resolve('promise1 callback'); }, 3000) }) } function promise2() { return new Promise((resolve, reject) => { console.log('promise2 start'); setTimeout(() => { return resolve('promise2 callback'); }, 2000) }) } async function asyncDemo() { console.log('function start'); let res1 = await promise1(); console.log('promise1 end', res1); let res2 = await promise2(); console.log('promise2 end', res2); console.log('function end); return 'finish'; } asyncDemo().then(res => { console.log(res) });

まず、asyncDemoをGenerator関数に変更します。

function* asyncDemoGen() { console.log('function start'); let res1 = yield promise1(); console.log('promise1 end', res1); let res2 = yield promise2(); console.log('promise2 end', res2); console.log('function end); return 'finish'; }

冒頭で述べたように、async/awaitは実はGenerator関数のための構文上の糖分なので、Thunk関数を実装してみましょう。 asyncで始まる関数はPromiseオブジェクトを返します。

function obj(foo) { return new Promise(function (a, b) { const bar = foo(); function c(Foo) { let A = bar.next(Foo); if (A.done) { return a(A.value); } c(A.value); } c(undefined); }); }

awaitの後には非同期関数が続く可能性が高いので、非同期関数がコールバックした後に実行されるべきです。 そこで、next

function asyncDemoThunk(fn) { return new Promise(function(resolve, reject) { const root = fn(); function next(data) { ... Promise.resolve(result.value).then(res => { next(res); }); } next(undefined); }) }

完全なコードは次のとおりです。

function asyncDemoThunk(fn) { return new Promise(function(resolve, reject) { const root = fn(); function next(data) { let result = root.next(data); if (result.done) { return resolve(result.value); } Promise.resolve(result.value).then(res => { next(res); }) } next(undefined); }) }

まとめ

実は、async/awaitはGeneratorの構文上の糖分であることは冒頭で述べたとおりで、asyncは関数をGenerator関数に変換し、関数内のawaitはyieldに置き換わります。 ここでは、その原理についても簡単に説明していますが、その目的は、async/awaitの理解を深めてもらうだけでなく、それを共有してもらうことを期待しています!async/awaitの理解を深め、それを共有できればと思います。 もし不備があれば、それを修正していただければと思います。

Read next

よくある質問のフロントエンドまとめ

現在のコンポーネントは、新しい仮想DOMツリーを生成し、Vueフレームワークは、トラバースし、新しい仮想DOMツリーと古い仮想DOMツリーの各ノードとの違いを比較し、それらを記録し、最後に、ロード操作、すべての記録された違いは、実際のDOMツリーにローカルの変更。 双方向バインディングを実現することは可能ですが...

Aug 28, 2020 · 9 min read