ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScript で独自の Promise を作成する
なぜ?
JavaScript Promise が内部でどのようにコールバックを非同期的に実行するかを理解するため。
JavaScript で独自の Promise を作成しましょう! Promise/A 仕様に従います。これは、Promise が非同期操作を処理し、解決、拒否し、予測可能なチェーンとエラー処理を保証する方法を概説しています。
話を簡単にするために、Promises/A 仕様の ✅ でマークされている主要なルールに焦点を当てます。これは完全な実装ではなく、簡略化されたバージョンになります。構築するものは次のとおりです:
1.1 'promise' は、動作がこの仕様に準拠する then メソッドを持つオブジェクトまたは関数です。
1.2 thenable' は、then メソッドを定義するオブジェクトまたは関数です。
1.3 「値」は、任意の有効な JavaScript 値 (未定義、thenable、または Promise を含む) です。
1.4 「例外」は、throw ステートメントを使用してスローされる値です。
1.5 'reason' は、Promise が拒否された理由を示す値です。
Promise は、保留中、履行済み、拒否の 3 つの状態のいずれかである必要があります。
2.1.1.保留中の場合、約束: ✅
⟶は、満たされた状態または拒否された状態のいずれかに移行する可能性があります。
2.1.2.果たされたときの約束: ✅
⟶は他の状態に遷移してはなりません。
⟶には値が必要であり、変更することはできません。
2.1.3.拒否された場合の約束: ✅
⟶は他の状態に遷移してはなりません。
⟶には理由があり、それを変えてはなりません。
Promise は、その現在または最終的な値または理由にアクセスするための then メソッドを提供する必要があります。
Promise の then メソッドは 2 つの引数を受け入れます:
promise.then(onFulfilled, onRejected);
2.2.1. onFulfilled と onRejected は両方ともオプションの引数です: ✅
⟶ onFulfilled が関数ではない場合、無視する必要があります。
⟶ onRejected が関数ではない場合、無視する必要があります。
2.2.2. onFulfilled が関数の場合: ✅
⟶ 最初の引数として Promise の値を指定して、Promise が実行された後に呼び出さなければなりません。
⟶ 約束が果たされる前に呼び出してはなりません。
⟶ 複数回呼び出すことはできません。
2.2.3. onRejected が関数の場合、✅
⟶ Promise が拒否された後に、Promise の理由を最初の引数として呼び出す必要があります。
⟶ Promise が拒否される前に呼び出してはなりません。
⟶ 複数回呼び出すことはできません。
2.2.4. onFulfilled または onRejected は、実行コンテキスト スタックにプラットフォーム コードのみが含まれるまで呼び出さないでください。 ✅
2.2.5. onFulfilled と onRejected は関数として呼び出す必要があります (つまり、this 値なし)。 ✅
2.2.6. then は同じ Promise で複数回呼び出される可能性があります。 ✅
⟶ Promise が履行された場合、それぞれの onFulfilled コールバックはすべて、then への呼び出しの順序で実行する必要があります。
⟶ Promise が拒否された場合、すべての onRejected コールバックは、最初の then 呼び出しの順序で実行する必要があります。
2.2.7.その後、Promise を返さなければなりません。 ✅
promise.then(onFulfilled, onRejected);
⟶ onFulfilled または onRejected のいずれかが値 x を返した場合、Promise 解決プロシージャ [[Resolve]](promise2, x) を実行します。 ❌
⟶ onFulfilled または onRejected のいずれかが例外 e をスローした場合、promise2 は e を理由として拒否されなければなりません。 ❌
⟶ onFulfilled が関数ではなく、promise1 が履行される場合、promise2 はpromise1 と同じ値で履行されなければなりません。 ❌
⟶ onRejected が関数ではなく、promise1 が拒否された場合、promise2 は、promise1 と同じ理由で拒否されなければなりません。 ❌
JavaScript Promise は引数として実行関数を受け取り、Promise が作成されるとすぐに呼び出されます。
promise2 = promise1.then(onFulfilled, onRejected);
new Promise(excecutor);
コアとなる Promises/A 仕様では、Promise を作成、履行、または拒否する方法については扱っていません。それはあなた次第です。ただし、Promise の構築のために提供する実装は、JavaScript の非同期 API と互換性がある必要があります。これが Promise クラスの最初のドラフトです:
const promise = new Promise((resolve, reject) => { // Runs some async or sync tasks });
ルール 2.1 (約束の状態) では、約束は保留、履行、または拒否の 3 つの状態のいずれかでなければならないと規定されています。また、これらの各状態で何が起こるかについても説明します。
Promise は、履行または拒否された場合、他の状態に移行してはなりません。したがって、移行を行う前に、Promise が保留状態であることを確認する必要があります。
class YourPromise { constructor(executor) { this.state = 'pending'; this.value = undefined; this.reason = undefined; const resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; } }; const reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; } }; try { executor(resolve, reject); // The executor function being called immediately } catch (error) { reject(error); } } }
Promise の初期状態が保留中であることはすでにわかっており、明示的に履行または拒否されるまで保留中のままであることを保証します。
const resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; } }; const reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; } };
Executor 関数は Promise のインスタンス化直後に呼び出されるため、コンストラクター メソッド内で呼び出します。
this.state = 'pending';
YourPromise クラスの最初のドラフトはここで完了します。
Promise/A 仕様は主に、相互運用可能な then() メソッドの定義に焦点を当てています。このメソッドを使用すると、Promise の現在または最終的な値または理由にアクセスできます。それでは、詳しく見ていきましょう。
ルール 2.2 (then メソッド) では、Promise には 2 つの引数を受け入れる then() メソッドが必要であると規定されています。
try { executor(resolve, reject); } catch (error) { reject(error); }
onFulfilled と onRejected は両方とも、promise が履行されるか拒否された後に呼び出す必要があります。関数の場合は、promise の値または理由を最初の引数として渡します。
class YourPromise { constructor(executor) { // Implementation } then(onFulfilled, onRejected) { // Implementation } }
さらに、Promise が履行または拒否される前に呼び出してはならず、複数回呼び出してはなりません。 onFulfilled と onRejected は両方ともオプションであり、関数でない場合は無視する必要があります。
ルール 2.2、2.2.6、および 2.2.7 を見ると、Promise には then() メソッドが必要であり、then() メソッドは複数回呼び出すことができ、メソッドは約束:
promise.then(onFulfilled, onRejected);
物事を簡単にするために、個別のクラスや関数は扱いません。 executor 関数を渡して、Promise オブジェクトを返します:
promise2 = promise1.then(onFulfilled, onRejected);
executor 関数内で、Promise が履行された場合、onFulfilled コールバックを呼び出し、Promise の値で解決します。同様に、Promise が拒否された場合は、onRejected コールバックを呼び出し、Promise の理由を指定して拒否します。
次の質問は、Promise がまだ保留状態にある場合に、onFulfilled コールバックと onRejected コールバックをどうするかということです。次のように、後で呼び出されるようにそれらをキューに入れます。
new Promise(excecutor);
これで完了です。これは、then() メソッドを含む Promise クラスの 2 番目のドラフトです:
const promise = new Promise((resolve, reject) => { // Runs some async or sync tasks });
ここでは、コールバックを保持するキューとして、onFulfilledCallbacks と onRejectedCallbacks という 2 つのフィールドを導入します。これらのキューには、Promise の保留中に then() 呼び出しを介してコールバックが設定され、Promise が履行または拒否されたときに呼び出されます。
Promise クラスをテストしてみましょう:
class YourPromise { constructor(executor) { this.state = 'pending'; this.value = undefined; this.reason = undefined; const resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; } }; const reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; } }; try { executor(resolve, reject); // The executor function being called immediately } catch (error) { reject(error); } } }
次のように出力されるはずです:
const resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; } }; const reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; } };
一方、次のテストを実行すると:
this.state = 'pending';
次の結果が得られます:
try { executor(resolve, reject); } catch (error) { reject(error); }
代わりに:
class YourPromise { constructor(executor) { // Implementation } then(onFulfilled, onRejected) { // Implementation } }
なぜですか?問題は、then() が呼び出された時点で YourPromise インスタンスがすでに解決または拒否されている場合に、then() メソッドがコールバックを処理する方法にあります。具体的には、Promise 状態が保留中でない場合、then() メソッドはコールバックの実行を次のマイクロタスク キューに適切に延期しません。そしてそれが同期実行を引き起こします。テスト例では:
⟶ Promise は、「即時解決」という値で即時に解決されます。
⟶ Promise.then() が呼び出されたとき、状態はすでに満たされているため、onFulfilled コールバックは次のマイクロタスク キューに延期されることなく直接実行されます。
ここで、ルール 2.2.4 が適用されます。このルールにより、Promise がすでに解決または拒否されている場合でも、then() コールバック (onFulfilled または onRejected) が非同期で実行されることが保証されます。これは、現在の実行スタックが完全にクリアされ、プラットフォーム コード (イベント ループやマイクロタスク キューなど) のみが実行されるまで、コールバックを実行してはいけないことを意味します。
このルールは、Promise/A 仕様の中で最も重要なルールの 1 つです。それは次のことを保証するためです:
⟶ Promise がすぐに解決されたとしても、その then() コールバックはイベント ループの次のティックまで実行されません。
⟶ この動作は、setTimeout や process.nextTick などの JavaScript の他の非同期 API の動作と一致しています。
これは、setTimeout や setImmediate などのマクロタスク メカニズム、または queueMicrotask や process.nextTick などのマイクロタスク メカニズムを使用して実現できます。マイクロタスク、マクロタスク、または同様のメカニズムのコールバックは、現在の JavaScript 実行コンテキストが終了した後に実行されるためです。
上記の問題を解決するには、状態がすでに満たされているか拒否されている場合でも、対応するコールバック (onFulfilled または onRejected) が queueMicrotask を使用して非同期に実行されるようにする必要があります。修正された実装は次のとおりです:
promise.then(onFulfilled, onRejected);
前のサンプル テスト コードを再度実行します。次の出力が得られるはずです:
promise2 = promise1.then(onFulfilled, onRejected);
以上です。
ここまでで、then() からのコールバックがどのように延期され、次のマイクロタスク キューで実行され、非同期動作が可能になるかについて明確に理解できたはずです。 JavaScript で効果的な非同期コードを作成するには、この概念をしっかりと理解することが不可欠です。
次は何ですか?この記事では Promises/A の仕様全体をカバーしていないため、残りの部分を実装して理解を深めてください。
ここまで読んでいただいたので、楽しんで読んでいただければ幸いです。記事をシェアしてください。
フォローしてください:
LinkedIn、Medium、Github
以上がJavaScript で独自の Promise を作成するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。