Promise, generator, async/await はどのように実行されるかについて
Published: 2022/3/12
microTask と (macro)Task
javascript の実行環境は、外部からイベントがあれば、それに対して callback するイベントループを基本として動作する。 この、一番外側のイベントによって発生する処理を Task, もしくは macroTask と呼ぶ。 macroTask は、おおむね queue みたいなものだと理解すれば良い。 (ただし、実行順序などは多少前後したりするかもしれないので、厳密には queue ではない)
また、特に Promise のために、 microTask というものも導入される。 これは、ある macroTask が完了して次の macroTask の処理にイベントループが移る前に、すべて解消するべき小さなタスクの queue として実装される。
なので、 javascript のランタイムは、大体以下のような挙動をする。
- while macroTask from macroTasks
- do macroTask
- while microTask in microTaskQueue
- do microTask
- repeat from beginning
ここで、 macroTask も microTask も実態は callback の関数なので、 タスクの実行とはそれを関数として呼び出すこと、と考えれば良い。
Promise とは: pending -> fulfill/rejected を伝播する機構
Promise 機能は、上記の microTask と macroTask の実行機構があったとすれば、 queueMicrotask
関数があればすべて実装できる。
Implement promise manually - Develop Paper
PromiseRepresents the final completion or failure of an asynchronous operation Define state const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; Constructor definition function MyPromise(excutor){ //The initial state is pending this.status = PENDING; //Value of success this.value = undefined; //Reasons for failure this.reason = undefined; //Then's collection of callback functions, which will […]
developpaper.com
Promise のデータ構造はおおまかに以下の通りであって、
interface Promise {
state: 'pending' | 'fulfulled' | 'rejected'
value?: Data
reason?: Error
onResolveCallbacks: ((Data) => void)[]
onErrorCallbacks: ((Error) => void)[]
}
resolve された瞬間に、それまで自身に対して .then
して生成された Promise たちを、その .then
の引数関数を実行した結果でもって resolve していくような処理を queueMicrotask する、というのが処理の中心部分。
queueMicrotask があることによって、例えば以下のコードを実行しても Promise の中の式は後から実行されるので、 promise 中のコードによる影響は、後から実行されるものとしてコードを整理すれば良くなる。
const foo () = {
let a = 1
Promise.resolve({}).then(() => {
a = 2
})
console.log(a) // ==> 1 が表示
}
generator の実装
Javascript stack model for generators
While I was using javascript generators to implement a debugger for a small scheme interpreter I starting wondering about the stack model in e.g. the chrome javascript engine. Normally It's enough to
stackoverflow.com

例えば、 generator は以下のようなコードであるが、
function* myGen() {
yield 1
yield 2
return 3
}
これは、 yield のタイミングで return と同じように .next()
の呼び出し元に戻り、もう一度 next()
を呼ばれた際には、
その関数の前回の yield
の直後から実行を再開するような機構があれば、これは素直に stack 上に再現できる。
というのも、このように途中から関数の実行を再開するような呼び出しを行うために必要なのは、すべてのローカル変数をクロージャ的にヒープ上に確保しておけば、 generator を実行して得られる iterator オブジェクト自身に前回の yield 場所を保持し、また runtime として(generator)関数の途中からの再開処理を実装すれば、普通に実装が可能。
async/await の実装
generator 処理が実装できている javascript のランタイムがあれば、それは、関数の途中から実行を再開することが可能になっていることに他ならない。
async/await もなので、似たような機構で実装されていて、ただ違いとしては以下の通り。
- await のタイミングで、 await した対象に対して
.then
して、自身の残りを再開するような処理を行う。 - await の戻り値は、 1 の結果得られる自身の再開処理の Promise オブジェクト。
補足: それぞれの transpile の方法
機能 | 説明 |
---|---|
Promise | queueMacrotask を setImmediate ないし setTimeout で代用 |
Generator | generator の中身を構文解析して、 switch 文で等価な表現を行い、かつそれを generator としてふるまわせる runtime を用意 |
async / await | promise と generator で等価に変換可能 |
I was curious how ES7’s works under the hood, but then I quickly realized this goes deeper than I had initially thought. It’s a good idea…
cmichel.io

In short, ES7’s syntax is used to turn asynchronous code flows with into synchronous ones. The proposal and examples can be seen here. We…
cmichel.io

Tags: javascript
関連記事
Promise 同時実行制御のための async-pool
2023/9/30
Promise を扱っている時に microQueue が積まれるタイミング
2022/10/10
Node.js でコマンドの実行結果を1行ずつ取得する方法
2022/10/9
(async) 非同期関数と非同期ジェネレータまとめ
2022/6/30