はじめに
Promiseオブジェクト生成時に渡す関数(executor)っていつ実行されるんだっけ、とか、executor完了後に「.then()」「.error()」でコールバック関数を登録した場合ってどうなるんだっけ、という点が個人的に曖昧だったので整理しました。
関連情報、および確認用のコードをまとめます。
Promise自体の説明については、以下のドキュメントが丁寧で分かりやすいと思います。
JavaScript Promiseの本
https://azu.github.io/promises-book/
公式はこちら。
Promise - JavaScript | MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Promiseの基本形/用語
Promiseに関わる要素のうち、明示的な呼称が無いものが存在します。
本稿では各要素について下記のように呼称することとします。
// Promiseオブジェクト生成関数を定義する
const createPromise = () => {
return new Promise((resolve, reject) => { // 本稿ではこの関数を executor と呼ぶ。
// executor は2つの引数を持ち、その内容はいずれも関数。
// 本稿ではそれぞれそのまま resolve(), reject() と呼ぶ。
//
// executor 内の処理が正常終了した場合は第1引数である resolve() を、異常終了した場合は第2引数である reject() を実行するよう実装する。
try {
// なにか処理を実行する
// ...
// ...
// executor 内の処理が正常終了したときに以下のように実行
// ここで渡した引数は、Promiseオブジェクトの「.then()」に渡す関数内から、第1引数として参照できるようになる
resolve('This executor is resolved.');
} catch (e) {
// executor 内の処理が異常終了したときに以下のように実行
// ここで渡した引数は、Promiseオブジェクトの「.catch()」に渡す関数内から、第1引数として参照できるようになる
reject('This executor is rejected.');
}
});
};
// Promiseオブジェクトを生成し、実行する
const promiseObject = createPromise();
// executor 実行完了後の処理をハンドリングする
promiseObject
.then((resolvedValue) => { // 本稿ではこの関数を onFullFilled関数 と呼ぶ。
// executor の処理が正常終了している場合、この関数内の処理が実行される。
// この関数の第1引数(この例ではresolvedValue)には、executor 内で実行された resolve() に渡された値が渡される。
// つまりこの例では 'This executor is resolved.' が入っている。
})
.catch((rejectedValue) => { // 本稿ではこの関数を onRejected関数 と呼ぶ。
// executor の処理が異常終了している場合、この関数内の処理が実行される。
// この関数の第1引数(この例ではrejectedValue)には、executor 内で実行された reject() に渡された値が渡される。
// つまりこの例では 'This executor is rejected.' が入っている。
});
上記コードから用語を抜き出すと下記のようになります。
用語 | 説明/内容 | 備考 |
---|---|---|
Promiseオブジェクト生成関数 | 上記コードの createPromise() 関数。 | |
executor | Promise生成時に第1引数として渡す関数。 | MDNドキュメント上も executor と呼ばれている。 |
resolve() | executorの第1引数。通常、executorが正常終了した場合に実行されるよう実装する。 | MDNドキュメント上は resolutionFunc と呼ばれている。 |
reject() | executorの第2引数。通常、executorが異常終了した場合に実行されるよう実装する。 | MDNドキュメント上は rejectionFunc と呼ばれている。 |
Promiseオブジェクト | 上記コードの promiseObject が該当。 | |
onFullFilled関数 | Promiseオブジェクトの .then() メソッドの第1引数として渡される関数。 executorが正常終了している場合、つまりresolve()の実行が完了したのちに実行される処理が渡される。 | |
onRejected関数 | Promiseオブジェクトの .catch() メソッドの第1引数として渡される関数。 executorが異常終了している場合、つまりreject()の実行が完了したのちに実行される処理が渡される。 |
確認したかったこと
- Promise生成時に渡す関数(executor)はいつ実行されるか
- Promiseオブジェクトを生成した時点で実行される
- Promiseの「.then()」「.error()」メソッドによる、onFullFilled関数/onRejected関数の登録が完了する前に、executorの実行が完了した場合、どうなるか
- executorは特に問題なくそのまま完了し、PromiseオブジェクトはSettled(FullFilled / Rejectedのいずれか)の状態になる。
- Promiseオブジェクトには、executor内のコールバック関数(つまりresolve() / reject())に渡された値が保持される。
- 「.then()」「.error()」メソッドにてonFullfilled関数/onRejected関数が登録されたのち、登録されたコールバック関数は非同期で即座に実行される。
- onFullFilled関数/onRejected関数は、Promiseオブジェクトに紐づけられた値を参照しながら、特に問題なく処理を実行し完了する。
確認した事項
Promise生成時に渡す関数(executor)はいつ実行されるか
executorはPromiseオブジェクト生成中に実行されるとのことです。
ということは、Promiseオブジェクトの生成が完了したときには、すでにexecutorは実行を開始しているということです。
Constructor Syntax
var promiseObj = new Promise(executor);
Arguments
executor
A function to be executed by the constructor, during the process of constructing the promiseObj. The executor is custom code that ties an outcome to a promise. You, the programmer, write the executor. The signature of this function is expected to be:
Promise - JavaScript | MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#Constructor_Syntax
onFullFilled関数/onRejected関数がいつ実行されるか
「.then()」「.error()」で登録されたコールバック関数は、登録直後に非同期で実行される。
JavaScript Promiseの本 - 2.3. コラム: Promiseは常に非同期?
https://azu.github.io/promises-book/#promise-is-always-async
onFullFilled関数/onRejected関数の登録前に、executorの実行が完了した場合、どうなるか
端的に説明した記述が見当たらなかったので、次のサンプルコードで確認しました。
結論としては、executor内でresolve()若しくはreject()を実行した際の引数を、onFullFilled関数/onRejected関数内から参照することができ、問題なく処理が完了します。
確認用サンプルコード
コード
以下のようなサンプルコードで確認しました。
// Promise オブジェクトを返却する関数 createPromise を作成する
const createPromise = () => {
return new Promise((resolve, reject) => {
// executor 実行時の時刻を出力する。
console.log(
"2. At the top of the executor() of a Promise object. at " + Date.now()
);
// 後続の処理を実行し、最後にresolve()を実行する。
// 一般的なユースケースとあわせるため、100ミリ秒後に後続の処理が完了することとした。
// ただし、Promise.then() / .error() によるコールバック関数登録前に完了する。
setTimeout(() => {
console.log(
"5. At the function called delayed within the executor(). at " + Date.now()
);
// executor内の関数でresolve()が実行された時刻が分かるよう、UNIXエポックミリ秒を引数に渡す。
resolve({message: "The executor has finished.", unixtime: Date.now(),});
// reject()実行時の動作確認用。
// reject({message: "The executor has failed.", unixtime: Date.now(),});
}, 100);
});
};
// createPromise 関数を実行し、Promiseオブジェクトを生成する。
console.log("1. Create a Promise object. at " + Date.now());
let promiseObject = createPromise();
console.log("3. A Promise object has been created. at " + Date.now());
// Promise.then() / .error() によりコールバック関数を登録する。
// コールバック関数の登録は、400ミリ秒後に完了する
setTimeout(() => {
console.log("6. Registering the onFullFilled and onRejected callback functions has been started. at " + Date.now());
promiseObject
.then((resolvedValue) => { // onFullFilled関数
console.log(
"7. The onFullFilled function has finished. " + JSON.stringify(resolvedValue) + " at " + Date.now()
);
})
.catch((rejectedError) => { // onRejected関数
console.log(
"7. The onRejected function has finished. " + JSON.stringify(rejectedError) + " at " + Date.now()
);
});
}, 400);
console.log("4. At the end of this program. at " + Date.now());
実行結果
実行結果は下記のとおりです。
処理開始から102ミリ秒後にexecutor関数が完了しています。(ここで言う「完了」とは、resolve()若しくはreject()が実行されたことを指します。)
executor内で最終的にresolve()が実行されたのは103ミリ秒経過後であることが、7行目のログから分かります。
その後、処理開始から402ミリ秒後、「.then()」及び「.error()」メソッドによるonFullFilled関数/onRejected関数の登録が行われています。
その後、登録されたonFullFilled関数も実行され、これは処理開始から402ミリ秒後であることが分かります。
1. Create a Promise object. at 1589122444638 // Promiseオブジェクト生成開始
2. At the top of the executor() of a Promise object. at 1589122444639 // executor実行開始
3. A Promise object has been created. at 1589122444639 // Promiseオブジェクト生成完了
4. At the end of this program. at 1589122444639 // コードの最終行に到達
5. At the function called delayed within the executor(). at 1589122444740 // executor内の処理がすべて完了。開始から102ミリ秒経過後
6. Registering the onFullFilled and onRejected callback functions has been started. at 1589122445040 // onFullFilled関数/onRejected関数の登録開始。開始から402ミリ秒経過後
7. The onFullFilled function has finished. {'message':'The executor has finished.','unixtime':1589122444741} at 1589122445040 // onFullFilled関数の実行完了。実行開始から402ミリ秒後に完了。executor内のresolve()を実行したのは開始から103ミリ秒経過後であることが併せてわかる。