【问题标题】:Awaited Promise Assignment/Passing vs Simply Awaiting等待的承诺分配/传递与简单等待
【发布时间】:2021-02-10 06:36:18
【问题描述】:

当使用对await 的赋值Promise 与仅使用await 而不使用赋值时会发生什么?取下面的代码:

const getSomething = async () => {
  let todos = null;
  await fetch('http://jsonplaceholder.typicode.com/todos/1')
    .then((response) => response.json())
    .then(
      (data) => {
        todos = data
      }
    );
  return todos;
};

const getSomethingElse = async () => {
  let todos = null;
  await fetch('http://jsonplaceholder.typicode.com/todos/2')
    .then((response) => response.json())
    .then(
      (data) => {
        todos = data
      }
    );
  return todos;
};

const doSomething = async () => {
  const somethingPromise = getSomething();
  const somethingElsePromise = getSomethingElse();
  const something = await somethingPromise;
  const somethingElse = await somethingElsePromise;

  console.log(something);
  console.log(somethingElse);
}

doSomething();

输出是

{ userId: 1, id: 1, title: 'delectus aut autem', completed: false }
{
  userId: 1,
  id: 2,
  title: 'quis ut nam facilis et officia qui',
  completed: false
}

但是,如果我们将 doSomething() 函数更改为简单的 await 两个 Promises,如下所示:

const doSomething = async () => {
  const somethingPromise = getSomething();
  const somethingElsePromise = getSomethingElse();

  await somethingPromise;
  await somethingElsePromise;
  
  console.log(somethingPromise);
  console.log(somethingElsePromise);
}

我们得到输出:

Promise {
  { userId: 1, id: 1, title: 'delectus aut autem', completed: false }
}
Promise {
  {
    userId: 1,
    id: 2,
    title: 'quis ut nam facilis et officia qui',
    completed: false
  }
}

为什么我们看到这些包裹在 Promises 中?

如果我们要简单地这样做,就可以把重点放在家里:

  const somethingPromise = getSomething();
  const somethingElsePromise = getSomethingElse();
  
  console.log(await somethingPromise);
  console.log(await somethingElsePromise);

我们会得到以下的原始输出:

{ userId: 1, id: 1, title: 'delectus aut autem', completed: false }
{
  userId: 1,
  id: 2,
  title: 'quis ut nam facilis et officia qui',
  completed: false
}

从中可以看出没有包装Promises

总而言之,我想了解“awaitedPromise 在分配给变量或通过函数(例如console.log)传递时会发生什么,而不是仅仅是 'awaited' 没有赋值或参数传递。

编辑 下面我展示了doSomething() 的三个版本和不同的输出。

第一个版本:

const doSomething = async () => {
  const somethingPromise = getSomething();
  const somethingElsePromise = getSomethingElse();
  const something = await somethingPromise;
  const somethingElse = await somethingElsePromise;

  console.log(something);
  console.log(somethingElse);
}

输出

{ userId: 1, id: 1, title: 'delectus aut autem', completed: false }
{
  userId: 1,
  id: 2,
  title: 'quis ut nam facilis et officia qui',
  completed: false
}

第二版:

const doSomething = async () => {
  const somethingPromise = getSomething();
  const somethingElsePromise = getSomethingElse();

  console.log(somethingPromise);
  console.log(somethingElsePromise);
}

输出

Promise { <pending> }
Promise { <pending> }

第三版:

const doSomething = async () => {
  const somethingPromise = getSomething();
  const somethingElsePromise = getSomethingElse();

  await somethingPromise;
  await somethingElsePromise;

  console.log(somethingPromise);
  console.log(somethingElsePromise);
}

输出:

Promise {
  { userId: 1, id: 1, title: 'delectus aut autem', completed: false }
}
Promise {
  {
    userId: 1,
    id: 2,
    title: 'quis ut nam facilis et officia qui',
    completed: false
  }
}

【问题讨论】:

  • 为什么我们会看到这些包装在 Promise 中?”因为你从不unwrap它们。承诺就是承诺,并且永远如此。您可以通过await-ing 承诺 来获取价值,结果 就是价值。另一种方法是使用 .then()/.finally() 的 promise API。
  • 非常类似于我昨天提供的问题/答案hereasync 函数总是返回一个 Promise,而从该 Promise 中获取值的唯一方法是让调用者在 async 函数返回的 Promise 上使用 .then()await。来自async 函数的return 值只是成为async 函数已经返回的承诺的解析值。前面提到的答案中有更多解释。
  • 谢谢@VLAZ,但我的问题是关于理解函数 doSomething() 的两个版本之间的区别,这与我们仅等待 Promise 与将它们分配或传递到功能类似于 console.log()。
  • 谢谢@jfriend00,但我真的只是想了解简单地说“await VariableName”与“const x = await VariableName”之间的区别
  • @MichaelCameron 你有一张披萨的收据——这是你的承诺。它会到,你会有比萨饼,但你现在没有。然后,您将awaitawait receipt 发送比萨饼。您现在查看收据 - 它仍然是收据,而不是比萨饼。这就是 Promise 的工作原理。

标签: javascript node.js asynchronous async-await


【解决方案1】:

Promise 和它所持有的值是有区别的。

let p = Promise.resolve(1);
await p;

promise 持有的值是 1。而 p 是已经解析为 1 的 promise。如果您按原样打印p,您会看到如下内容:

这里要注意的重要一点是,promise 已经解决并且已经实现了。

这是一个重要的区别,所以我将展示替代方案。 已解决的承诺(因此未履行或拒绝)。

let f;
let p = new Promise((fulfill) => {
   f = fulfill;
}); 

如果你要打印p,你会看到承诺没有解决。

如果我用一个值调用f,我会用那个值解析p

f(1)

现在您的印象是,如果您不存储值而只存储 await 承诺,await 的功能会有所不同。

let result = await p;
console.log(result); 

对比:

await p;
console.log(p);

当然,不同之处在于await 将评估为承诺p 的已实现值(或者如果它被拒绝,它将抛出)。所以result 不是一个承诺。但p 仍然是一个承诺,await 不会对p 做任何事情,除了检索值。

await 不会改变p,它不会使p 成为1

【讨论】:

  • 非常感谢!!!那么,您调用f() 并将Promise 置于fulfilled 状态的部分是否对应于我的doSomething() 函数输出的第三个版本?问题?
  • 嗯,不完全是。 f 或 promise 构造函数中 resolve 执行器的第一个参数的值是 promise 被实现为特定值(可以是另一个 promise)的机制。 await 将保证承诺已解决,而 f 是履行承诺(并因此解决)的人。
  • 尽管在我的示例中,在您的第三种情况和调用f 的结果中,promise 都将得到解决,并且大多数打印promise 的实用程序(比如console)也将打印完成的值。
【解决方案2】:

在第一个版本中,您等待承诺并将等待的结果分配给一个变量并记录该变量。这会将 promise 中的已解析值分配给您的变量。您的变量不是承诺,而是已解决的值。因此,您会在日志中看到解析后的值。

在第二个版本中,您只是记录了 Promise,而 Promise 背后的异步操作尚未解决,因此您会看到一个待处理的 Promise。您的变量只包含承诺。

在第三个版本中,您将 Promise 分配给两个变量,然后等待每个变量允许这两个 Promise 解决,然后再次记录 Promise。因此,这一次,当您记录承诺时,它们已被履行,并且承诺的内部知道其价值。

请记住,仅仅因为第三个版本记录了一个带有值的承诺,这对您的代码没有任何意义。如果不对承诺使用.then()await,就无法从承诺中获得价值。 console.log() 拥有对 Javascript 程序员所没有的 promise 内部的特权访问。如果你想从 Promise 中取出值,你必须使用以下两种形式之一:

const x = await somePromise;
console.log(x);

somePromise.then(x => {
    console.log(x);
});

总之,我想了解当“等待”的 Promise 被分配给变量或通过函数(例如 console.log)时会发生什么,而不是在没有赋值或参数的情况下被“等待”-路过。

当你这样做时:

const x = await somePromise;

您没有为变量x 分配等待的承诺。该值不是承诺。您正在将 promise 的已解析值分配给您的变量 x。它现在只包含一个值。承诺仍在somePromise 中,它将具有包含已解析值的内部状态。您的变量现在只包含解析的值。诺言没有改变。相反,解释器暂停了函数的执行,直到 promise 解决,然后它从 promise 中获取已解决的值并将该已解决的值放入变量 x,然后恢复函数的执行。

【讨论】:

  • 好的,但特别是关于第三个版本。为什么它被包装在一个 Promise 中,正如你所说,它的内部是已知的,而如果我要在分配的上下文中使用 await - 例如在版本 1 中 - 它不会是?
  • @MichaelCameron - 在第三个版本中,您仍然只是记录承诺。它仍然是和以前一样的 promise 对象。它现在有更多的内部状态,因为它现在已经解决了,但它仍然是它一直以来的相同承诺。这就像在做x = {greeting: "hello"}; console.log(x); x.greeting = "goodbye"; console.log(x)`。您仍然只是在记录同一个对象,并且它的内部结构已经改变。仅供参考,promise 只是一个 Javascript 对象,它具有一些内部私有属性。当一个 promise 得到解决时,它的内部状态会发生变化,但它仍然是对象。
  • 第二个和第三个的区别展示了 Promise 对象可以持有的三个 state 中的两个。 Pending 状态表示承诺尚未解决或拒绝。您的第三个示例仅显示 Promise 对象已被解决,并且已被解决或被拒绝。
  • 不客气。我们都以不同的方式思考/感知这些东西。
  • @MichaelCameron - 另外,请记住,console.log() 正在为承诺进行特殊记录。它不会向您展示其中的所有内容。里面还有很多其他的东西,比如.then().catch() 的注册听众列表。在承诺解决之前,console.log() 的这个特定实现选择只显示Promise { &lt;pending&gt; }。一旦 promise 被解决,它就会改变它的显示以包含已解决的值。这只是console.log() 的实现。
【解决方案3】:

Promise 对象只能存在于以下三种状态之一:

  1. 待处理
  2. 已实现(已解决)
  3. 拒绝

使用async/await 允许我们暂停async 函数中的操作,直到被await'd 的Promise 从Pending 状态转移到已完成或已拒绝状态。

因此,在您的第一个示例中,带有await 的赋值语句会暂停,直到状态发生变化。如果 Promise 被履行,则分配的左侧将被分配从履行的 Promise 返回的值。

第二个和第三个之间的区别也说明了 Promise 对象可以持有的三个状态中的两个。 Pending 状态意味着承诺尚未解决或拒绝。您的第三个示例仅显示 Promise 对象已被解决,并且已被解决或被拒绝。

附带说明,如果 Promise 在使用 async/await 时被拒绝,它将抛出。所以await 调用应该总是包裹在try/catch 块中。

【讨论】:

    【解决方案4】:

    为什么我们会看到这些包装在 Promise 中?

    因为您正在记录保存承诺的变量,而不是从 await 表达式返回的承诺的结果。

    将“等待”的 Promise 分配给变量或通过函数(例如 console.log)而不是仅仅“等待”时会发生什么

    承诺没有发生任何事情。它仍然是一个承诺对象,它不会“成为”结果值。没有任何神奇的转换可以让somethingPromise 现在引用结果 JSON 对象。

    当你使用await 操作符时,会调用promise 的.then() 方法,它根本不会改变promise 对象,而是安排一个回调来运行。然后async function 的执行被暂停,只有在回调运行后才恢复,await 表达式的计算结果是作为参数传递给回调的结果值。是否将此值分配给变量,将其传递给函数,或者只是忽略它,都是您的选择。

    await 运算符不返回其右侧操作数。

    【讨论】:

    • 但是,如果 await 运算符由于某种原因在内存中分配了一个位置,它会返回它的操作数吗?
    • 不,它不返回其操作数。它等待承诺并返回承诺的结果——这与承诺对象本身不同。 Promise 背后没有魔法,它们只是可以在用户空间中实现的普通对象。
    • 太棒了,有道理
    猜你喜欢
    • 1970-01-01
    • 2013-09-16
    • 2019-12-10
    • 1970-01-01
    • 2020-04-30
    • 1970-01-01
    • 2019-04-29
    • 1970-01-01
    • 2019-04-19
    相关资源
    最近更新 更多