これらの偉大な2つのソースに関して、 NZakas-Promise ChainsでPromiseを返す および MDN Promises について、次の質問をしたいと思います。
約束履行ハンドラーから値を返すたびに、その値は同じハンドラーから返される新しい約束にどのように渡されますか?
例えば、
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
let p2 = new Promise(function(resolve, reject) {
resolve(43);
});
let p3 = p1.then(function(value) {
// first fulfillment handler
console.log(value); // 42
return p2;
});
p3.then(function(value) {
// second fulfillment handler
console.log(value); // 43
});
この例では、p2
はプロミスです。 p3
は、p1
のフルフィルメントハンドラーからのプロミスでもあります。ただし、p2 !== p3
。代わりにp2
は何らかの方法で43
に魔法のように解決し(方法?)、その値はp3
のフルフィルメントハンドラーに渡されます。ここの文章でさえ混乱を招きます。
ここで何が起こっているのか正確に説明してもらえますか?私はこの概念について完全に混乱しています。
then()
コールバック内にスローすると、失敗した結果プロミスが拒否され、then()
コールバックから戻ると、成功値で結果プロミスが満たされるとしましょう。
let p2 = p1.then(() => {
throw new Error('lol')
})
// p2 was rejected with Error('lol')
let p3 = p1.then(() => {
return 42
})
// p3 was fulfilled with 42
しかし、継続中であっても、成功したかどうかわからない場合があります。もっと時間が必要です。
return checkCache().then(cachedValue => {
if (cachedValue) {
return cachedValue
}
// I want to do some async work here
})
ただし、そこで非同期作業を行う場合、return
またはthrow
には遅すぎますよね。
return checkCache().then(cachedValue => {
if (cachedValue) {
return cachedValue
}
fetchData().then(fetchedValue => {
// Doesn’t make sense: it’s too late to return from outer function by now.
// What do we do?
// return fetchedValue
})
})
別のPromiseに解決ができなかった場合、Promiseは役に立ちません。
あなたの例ではp2
がになるp3
になるという意味ではありません。これらは別個のPromiseオブジェクトです。ただし、p2
を生成するthen()
からp3
を返すことは、「成功するか失敗するかに関係なく、p3
が解決するものにp2
を解決したい」と言っていることです。
howこれについては、実装固有です。内部的には、then()
は新しいPromiseの作成と考えることができます。実装は、いつでも好きなときにそれを実現または拒否することができます。通常、あなたが戻ったときに自動的にそれを実現または拒否します:
// Warning: this is just an illustration
// and not a real implementation code.
// For example, it completely ignores
// the second then() argument for clarity,
// and completely ignores the Promises/A+
// requirement that continuations are
// run asynchronously.
then(callback) {
// Save these so we can manipulate
// the returned Promise when we are ready
let resolve, reject
// Imagine this._onFulfilled is an internal
// queue of code to run after current Promise resolves.
this._onFulfilled.Push(() => {
let result, error, succeeded
try {
// Call your callback!
result = callback(this._result)
succeeded = true
} catch (err) {
error = err
succeeded = false
}
if (succeeded) {
// If your callback returned a value,
// fulfill the returned Promise to it
resolve(result)
} else {
// If your callback threw an error,
// reject the returned Promise with it
reject(error)
}
})
// then() returns a Promise
return new Promise((_resolve, _reject) => {
resolve = _resolve
reject = _reject
})
}
繰り返しますが、これは非常に擬似的なコードですが、Promise実装でthen()
を実装する方法の背後にある考え方を示しています。
Promiseへの解決のサポートを追加する場合、then()
に渡すcallback
がPromiseを返した場合、特別なブランチを持つようにコードを変更するだけです。
if (succeeded) {
// If your callback returned a value,
// resolve the returned Promise to it...
if (typeof result.then === 'function') {
// ...unless it is a Promise itself,
// in which case we just pass our internal
// resolve and reject to then() of that Promise
result.then(resolve, reject)
} else {
resolve(result)
}
} else {
// If your callback threw an error,
// reject the returned Promise with it
reject(error)
}
})
これが実際のPromise実装ではなく、大きな穴と非互換性があることをもう一度明確にしましょう。ただし、PromiseライブラリがPromiseへの解決をどのように実装するかについての直感的なアイデアを提供する必要があります。アイデアに満足したら、実際のPromise実装 handle this を確認することをお勧めします。
基本的にp3
はreturn
であり、別のプロミスp2
になります。つまり、p2
の結果はパラメーターとして次のthen
コールバックに渡され、この場合は43
に解決されます。
キーワードreturn
を使用している場合は常に、結果をパラメーターとして次のthen
のコールバックに渡します。
let p3 = p1.then(function(value) {
// first fulfillment handler
console.log(value); // 42
return p2;
});
あなたのコード:
p3.then(function(value) {
// second fulfillment handler
console.log(value); // 43
});
等しい:
p1.then(function(resultOfP1) {
// resultOfP1 === 42
return p2; // // Returning a promise ( that might resolve to 43 or fail )
})
.then(function(resultOfP2) {
console.log(resultOfP2) // '43'
});
ところで、私はあなたがES6構文を使用していることに気づきました、太い矢印構文を使用することでより軽い構文を持つことができます:
p1.then(resultOfP1 => p2) // the `return` is implied since it's a one-liner
.then(resultOfP2 => console.log(resultOfP2));
この例では、p2は約束です。 p3は、p1のフルフィルメントハンドラーから発生する約束でもあります。ただし、p2!== p3。代わりに、p2は何らかの形で魔法のように43(how?)に解決され、その値はp3のフルフィルメントハンドラーに渡されます。ここの文章でさえ混乱を招きます。
これがどのように機能するかを簡略化したバージョン(擬似コードのみ)
function resolve(value){
if(isPromise(value)){
value.then(resolve, reject);
}else{
//dispatch the value to the listener
}
}
あなたは注意を払わなければならないので、全体はかなり複雑です。約束がすでに解決されているかどうか、さらにいくつかのこと。
私は質問「なぜthen
コールバックがPromise
s自体を返すことができるのか」にもっと正統的に答えようとします。別の角度をとるために、Promise
sを、より複雑でわかりにくいコンテナータイプであるArray
sと比較します。
Promise
は、将来の値のコンテナです。 Array
は、任意の数の値のコンテナです。
コンテナタイプに通常の機能を適用することはできません。
const sqr = x => x * x;
const xs = [1,2,3];
const p = Promise.resolve(3);
sqr(xs); // fails
sqr(p); // fails
特定のコンテナのコンテキストにそれらを持ち上げるメカニズムが必要です。
xs.map(sqr); // [1,4,9]
p.then(sqr); // Promise {[[PromiseValue]]: 9}
しかし、提供された関数自体が同じタイプのコンテナを返すとどうなりますか?
const sqra = x => [x * x];
const sqrp = x => Promise.resolve(x * x);
const xs = [1,2,3];
const p = Promise.resolve(3);
xs.map(sqra); // [[1],[4],[9]]
p.then(sqrp); // Promise {[[PromiseValue]]: 9}
sqra
は期待どおりに動作します。正しい値を持つネストされたコンテナを返すだけです。ただし、これは明らかにあまり有用ではありません。
しかし、どのようにsqrp
の結果を解釈できますか?独自のロジックに従う場合、Promise {[[PromiseValue]]: Promise {[[PromiseValue]]: 9}}
のようなものでなければなりませんでしたが、そうではありません。ここでどんな魔法が起こっているのでしょうか?
メカニズムを再構築するには、map
メソッドを少し調整するだけです。
const flatten = f => x => f(x)[0];
const sqra = x => [x * x];
const sqrp = x => Promise.resolve(x * x);
const xs = [1,2,3];
xs.map(flatten(sqra))
flatten
は、関数と値を取り、その関数を値に適用して結果をアンラップします。したがって、ネストされた配列構造を1レベル減らします。
簡単に言えば、then
sのコンテキストでPromise
は、map
sのコンテキストでflatten
と組み合わせたArray
と同等です。この動作は非常に重要です。 通常の関数をPromise
に適用できるだけでなく、それ自体がPromise
を返す関数も適用できます。
実際、これは関数型プログラミングの領域です。 Promise
はmonadの特定の実装であり、then
はbind
/chain
であり、Promise
を返す関数はモナド関数です。 Promise
APIを理解すると、基本的にすべてのモナドを理解できます。