承诺回调返回承诺

Promise callbacks returning promises

本文关键字:承诺 返回 回调      更新时间:2023-09-26

关于这两个伟大的来源:NZakas - 承诺链中的回报承诺和MDN承诺,我想问以下几个问题:

每次我们从承诺履行处理程序返回值

时,该值如何传递给从同一处理程序返回的新承诺?

例如

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
})

但是,如果我在那里做异步工作,returnthrow就太晚了,不是吗?

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
  })
})

这就是为什么如果你无法解析到另一个承诺,承诺将没有用。

这并不意味着在您的示例中p2变得p3。它们是单独的 Promise 对象。然而,通过从产生p3then()返回p2,你是在说"我希望p3解决p2解决的任何问题,无论它是成功还是失败"。

至于这是如何发生的,它是特定于实现的。在内部,你可以把then()看作是一个新的承诺。实现将能够随时满足或拒绝它。通常,当您返回时,它会自动履行或拒绝它:

// 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 实现如何处理这个问题。

基本上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(如何?),然后将该值传递给 p3 的履行处理程序。甚至这里的句子也令人困惑。

简化版本的工作原理(仅限伪代码)

function resolve(value){
    if(isPromise(value)){
        value.then(resolve, reject);
    }else{
        //dispatch the value to the listener
    }
}

整个事情要复杂得多,因为你必须小心,承诺已经解决了,还有一些事情。

我将尝试更规范地回答"为什么then回调可以返回Promise本身"的问题。为了从不同的角度,我将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只接受一个函数和一个值,将函数应用于值并解包结果,因此它将嵌套数组结构减少了一级。

简单地说,Promise s上下文中的then等同于mapArray s上下文中的flatten相结合。此行为非常重要。我们不仅可以将普通函数应用于Promise还可以将本身返回Promise的函数应用于 .

事实上,这是函数式编程的领域。Promisemonad的特定实现,thenbind/chain,返回Promise的函数是monadic函数。当你了解Promise API时,你基本上理解了所有的monads。