web-dev-qa-db-ja.com

なぜPromiseコンストラクターは、完了時に 'resolve'を呼び出す関数を必要とするのに、 'then'はそうではなく、代わりに値を返すのですか?

Promisesの研究に没頭すると、議論されていない次の質問で私の理解が止まりました(私が見つけたのはPromiseコンストラクターとPromise 'then'関数の特定の議論だけですが、設計パターンを比較する議論はありません) )。


1。 Promiseコンストラクター

MDNドキュメントからPromiseコンストラクターのこの使用法があります(コメントを追加):

new Promise(function(resolve, reject) { ... }); // <-- Call this Stage 1

2つの引数resolveおよびrejectを持つ関数オブジェクト。最初の引数は約束を満たし、2番目の引数はそれを拒否します。操作が完了したら、これらの関数を呼び出すことができます。


2。 then関数

thenオブジェクト(新しいPromiseオブジェクトを返す)で呼び出すことができるPromise関数に移動すると、 次の関数ドキュメントに記載されている署名 (私のコメントを追加)::

p.then(onFulfilled, onRejected);

連鎖

thenメソッドはPromiseを返すため、簡単に連鎖して呼び出しできます。

var p2 = new Promise(function(resolve, reject) {
  resolve(1); // <-- Stage 1 again
});

p2.then(function(value) {
  console.log(value); // 1
  return value + 1; // <-- Call this Stage 2
}).then(function(value) {
  console.log(value); // 2
});

私の質問

上記のコードスニペットから、ステージ1でresolve関数に渡された(2番目のresolve-の下( 2)、上記)は、次のステージ(同じコードスニペットで続く最初のthen関数)に渡されます。 ステージ1には戻り値はありません。ただし、ステージ2では戻り値です。その後の次のステージに渡されます(2番目のthen関数)。

Promiseを作成するための設計パターンと、既存のpromise(thenも返す)でのPromise関数の使用と、単なる歴史的な問題(コールバックを呼び出す必要があるが何も返さない)もう一方は値を返しますが、コールバックを呼び出しません)?

または、Promiseコンストラクターがthen関数とは異なる設計パターンを使用する根本的な理由がありませんか?

33
Dan Nissenbaum

Bergiの答え は素晴らしく、とても助けになりました。この答えは彼を補完するものです。 Promise()コンストラクターとthen()メソッドの関係を視覚化するために、この図を作成しました。それが誰かの助けになることを願っています...多分私も、今から数ヶ月後。

ここでの主なアイデアは、Promise()コンストラクターに渡される "executor"関数が、約束のset the stateを実行するタスクを設定することです。一方、then()に渡すハンドラーは、約束の状態に反応するになります。

Diagram: Promise() executor vs. then() methodJake Archibaldの古典的なチュートリアル から適合したコード例)

これは、物事の仕組みを非常に簡略化したビューであり、多くの重要な詳細は省略されています。しかし、意図した目的の概要を把握しておくことができれば、詳細を理解するときに混乱を避けることができます。

選択したいくつかの詳細

エグゼキューターはすぐに呼び出されます

重要な詳細の1つは、Promise()コンストラクターに渡されるexecutor関数がimmediately(コンストラクターがプロミスを返す前に)と呼ばれることです。一方、then()メソッドに渡されたハンドラー関数は、later(ある場合)まで呼び出されません。

Bergiはこれについて言及しましたが、a/synchronouslyの用語を使用せずに再説明したかったので、注意深く読んでいない場合は混乱する可能性があります:関数calling何かの非同期的な区別vs.非同期的に呼び出されるは、通信で簡単に見直すことができます。

resolve()onFulfill()ではありません

しばらく混乱していたため、強調したいもう1つの詳細は、resolve()およびreject()コールバックがPromise()コンストラクターのエグゼキューター関数に渡されることです。 are not後でthen()メソッドに渡されるコールバック。振り返ってみると、これは明らかなように思えますが、見た目のつながりが長すぎるために円を描くようになりました。接続は確かにありますが、ゆるく、動的な接続です。

代わりに、resolve()およびreject()コールバックは関数です"system"によって提供されますであり、Promiseによってexecutor関数に渡されます約束を作成するときのコンストラクタ。 resolve()関数が呼び出されると、潜在的にプロミスの状態を変更するシステムコードが実行され、最終的にonFulfilled()コールバックが非同期に呼び出されます。 resolve()を呼び出すことは、onFulfill()を呼び出すためのタイトなラッパーとは思わないでください!

26
LarsH

Promiseコンストラクターとthenメソッドは、異なる目的のために設計された2つの独立したものであるため、対応していません。

Promiseコンストラクターは promisifying にのみ使用されます1 非同期関数。実際、あなたが言うように、 invoking resolve/reject asynchronously send へのコールバックに基づいて構築されます。値、およびその場合の戻り値はありません。

Promiseコンストラクター自体がこの「リゾルバー」コールバック(resolverejectを同期的に渡す)を取ることは、実際には古い deferred pattern 、および意図しない類似性thenコールバックに対して。

var p = new Promise(function(res, rej) {    |    var def = Promise.Deferred();
    setTimeout(res, 100);                   |    setTimeout(def.resolve, 100);
});                                         |    var p = def.promise;

対照的に、thenコールバックは従来の非同期コールバックであり、 追加機能 からreturnを使用できます。 非同期的に呼び出されるから receive の値。

p.then(function(val) { … });

違いをまとめるには:

  • Promiseはコンストラクターであり、thenはメソッドです
  • Promiseは1つのコールバックを取り、thenは2つまでかかります
  • Promiseはコールバックを同期的に呼び出し、thenはコールバックを非同期的に呼び出します
  • Promiseは常にそのコールバックを呼び出します。
    thenは、コールバックを呼び出さない場合があります(約束が満たされない/拒否されない場合)
  • Promiseは、コールバックに対するプロミスを解決/拒否する機能を渡します。
    thenは、要求されたプロミスの結果値/拒否理由を渡します
  • Promiseは、副作用を実行する目的でコールバックを呼び出します(reject/resolveを呼び出します)。
    thenは、結果値のコールバックを呼び出します(チェーン用)

はい、どちらも約束を返しますが、他の多くの関数とその特性を共有しています(Promise.resolvePromise.rejectfetch、…)。実際、これらはすべて、Promiseコンストラクターが提供するものと同じプロミス構築および解決/拒否機能に基づいていますが、それは主な目的ではありません。 thenは基本的に、onFulfilled/onRejectedコールバックを既存のpromiseにアタッチする機能を提供します。これはPromiseコンストラクターに相当します。

両方がコールバックを利用するのは偶然です-歴史的な発言ではなく、言語機能の協調。

1:理想的には、すべてのネイティブ非同期APIがpromiseを返すため、これは必要ないでしょう

23
Bergi

Promiseコンストラクターのエグゼキューター関数のポイントは、resolveおよびreject関数を非プロミス使用コードに広め、それをラップし、プロミスを使用するように変換することです。これを同期関数のみに制限したい場合は、はい、関数からの戻り値を代わりに使用できますが、リゾルバーを配布し、実際に後で実行されるコードに関数を拒否することは愚かなことです(帰国後の道)、例えば非同期APIに渡されるコールバックへ。

6
jib

以前の回答に触発された(私が最も混乱した部分に対処します):

Promiseコンストラクターのresolveおよびreject引数は、ユーザーが定義する関数ではありません。それらを非同期操作コード(通常は成功応答のresolveと失敗理由のreject)に埋め込むフックと考えると、javascriptが最終的にPromiseをマークする方法を持ちます。非同期操作の結果に応じて、履行済みまたは拒否として;それが発生すると、then(fun1, fun2)で定義した適切な関数がトリガーされ、Promiseが消費されます(Promiseが履行/拒否されたかどうかに応じて、fun1(success_response)またはfun2(failure_reason)のいずれか)。 fun1およびfun2は単純な古いjavascript関数であり(非同期操作の将来の結果を引数として取得するだけです)、return値(明示的に返さない場合はundefinedにできます) 。

Mozillaのすばらしい記事もご覧ください。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

4
Yibo Yang