JavaScript await/async 执行顺序

6

所以我试图理解Promise/await/async。我不明白为什么当执行go()时,带有“完成”警报的部分会紧接着console.log(coffee)输出。为什么只等待getCoffee(),而其他axios调用在所有函数都使用await/promise时才会在“完成”警报之后运行?

function getCoffee() {
  return new Promise(resolve => {
    setTimeout(() => resolve("☕"), 2000); // it takes 2 seconds to make coffee
  });
}
async function go() {
  try {
    alert("ok");
    const coffee = await getCoffee();

    console.log(coffee); // ☕

    const wes = await axios("https://randomuser.me/api/?results=200");
    console.log("wes"); // using string instead of value for brevity

    const wordPromise = axios("https://randomuser.me/api/?results=200");
    console.log("wordPromise"); // using string instead of value for brevity

    alert("finish");
  } catch (e) {
    console.error(e); // 
  }
}
go();
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>


1
我尝试过这个程序,结果符合预期(尽管我使用的是Node而非浏览器,并将alert替换为console logs)。 - junvar
@junvar 我能够在浏览器中使用上面的堆栈片段重现所描述的行为。 - Patrick Roberts
2
可能是在axios调用后alert("finish")正确运行,但你没有及时在控制台中看到wes。向控制台记录不总是即时操作(无论您记录什么都必须进行处理、布局和绘制)。本质上,console.log可以被认为是一个保证按顺序运行的异步操作。尝试将console.log替换为alert或将alert替换为console.log,看看是否正确。 - stevendesu
1
@CI_Guy 如果您在运行 Stack Snippet 时打开开发人员工具,您还会看到当 finish 被警报时控制台日志也可见。 - Patrick Roberts
你是对的,认为连续使用await关键字会确保函数调用的顺序按照编写的顺序进行(而不是由于异步编程的复杂性导致顺序未知)。@stevendesu的答案是正确的,await的行为符合预期,但console.log可能不符合预期。请考虑接受此问题的编辑改进建议,因为问题可以比您提供的方式更清晰地编写。谢谢。 - Ninjaxor
显示剩余2条评论
2个回答

4
问题在于,console.log并不总是像人们想象的那样同步。规范仅要求console.log在开发者控制台中显示消息,但没有任何关于消息将如何或何时显示的要求。 根据您的浏览器,结果可能会有所不同,但通常实现方式如下:
  • 当您调用console.log时,请求会被推送到一个队列(因此连续调用console.log总是按顺序执行)
  • 在下一帧动画中,浏览器将尝试尽可能多地处理队列(队列中必须至少有一个元素被处理,因此如果您尝试记录8兆字节的数据,则浏览器可能会锁定)
  • “处理”队列涉及将DOM元素引用转换为链接,转换JSON对象为可导航和可折叠的UI元素,或将对象替换为文本“[Object object]”等操作
  • 一旦队列上的元素被处理,就必须在控制台中呈现它。这需要调整控制台的高度,确定是否需要滚动条,确定文本将包裹的位置等。 这个过程(将控制台中的内容展示在屏幕上)称为“painting”
由于console.log实际上是一个类似于此的复杂操作,因此在某些浏览器中它可能在alert语句运行之前就已经执行完毕。通过将每个alert调用替换为console.log或每个console.log调用替换为alert,您应该会发现事情实际上按预期顺序执行。

1
异步/等待功能正常工作。只是控制台需要一些时间来更新或浏览器正在重绘,因此在它可以重绘之前就会触发alert。您可以通过使用所有的alert而不是console.log来验证它。所有的alert都按正确顺序执行。如下面的示例所示。

function getCoffee() {
    return new Promise(resolve => {
      setTimeout(() => {resolve("coffee")}, 2000); // it takes 2 seconds to make coffee
    });
  }
  async function go() {
    try {
      alert("ok");
      const coffee = await getCoffee();

      alert(coffee); // ☕

      const wes = await getCoffee();
      alert(wes);

      const wordPromise = getCoffee();
      alert(wordPromise);

      alert("finish");
    } catch (e) {
      console.error(e); // 
    }
  }
go();


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接