web-dev-qa-db-ja.com

Promiseを返すPromiseコールバック

これらの偉大な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のフルフィルメントハンドラーに渡されます。ここの文章でさえ混乱を招きます。

ここで何が起こっているのか正確に説明してもらえますか?私はこの概念について完全に混乱しています。

53
kstratis

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への解決をどのように実装するかについての直感的なアイデアを提供する必要があります。アイデアに満足したら、実際のP​​romise実装 handle this を確認することをお勧めします。

38
Dan Abramov

基本的にp3returnであり、別のプロミス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)); 
18
Vincent Taing

この例では、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
    }
}

あなたは注意を払わなければならないので、全体はかなり複雑です。約束がすでに解決されているかどうか、さらにいくつかのこと。

4
Thomas

私は質問「なぜthenコールバックがPromises自体を返すことができるのか」にもっと正統的に答えようとします。別の角度をとるために、Promisesを、より複雑でわかりにくいコンテナータイプであるArraysと比較します。

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レベル減らします。

簡単に言えば、thensのコンテキストでPromiseは、mapsのコンテキストでflattenと組み合わせたArrayと同等です。この動作は非常に重要です。 通常の関数をPromiseに適用できるだけでなく、それ自体がPromiseを返す関数も適用できます。

実際、これは関数型プログラミングの領域です。 Promisemonadの特定の実装であり、thenbind/chainであり、Promiseを返す関数はモナド関数です。 Promise APIを理解すると、基本的にすべてのモナドを理解できます。

3
user6445533