代码之家  ›  专栏  ›  技术社区  ›  rb612

为什么javascript异步函数不立即返回?

  •  5
  • rb612  · 技术社区  · 5 年前

    我试图更好地理解js中的异步函数和承诺。为此,我编写了一个示例程序,其目标是调用一个繁忙工作的函数(故意不使用async setTimeout 因为我想模拟一个长时间运行的进程),但会立即返回。然而,我似乎不明白为什么这不起作用。

        test();
        
        async function intense(){
          var start = new Date().getTime();
          for (var i = 0; i < 1e6; i++) {
            if ((new Date().getTime() - start) > 2000){
              break;
            }
          }
          console.log("Done with async work");
        }
        async function test(){
            console.log("Print 1");
            intense(); // does some busy work for a few seconds
            console.log("Print 2"); // want this to print immediately after print 1
        }

    当我运行它时,我得到:

    Print 1
    Done with async work
    Print 2
    

    我希望是:

    Print 1
    Print 2
    Done with async work
    

    我以为它会打印后一个序列,因为我声明了 intense() 是异步的,所以它会立即返回一个承诺并继续异步工作。

    我甚至试着重构 intense 功能是一个承诺,立即解决,但无济于事。

    async function intense(){
      return new Promise((resolve)=> {
          resolve();
          var start = new Date().getTime();
          for (var i = 0; i < 1e6; i++) {
            if ((new Date().getTime() - start) > 2000){
                break;
            }
      }
      console.log("Done with async work");
    }, null)
    }
    

    我错过了什么?

    3 回复  |  直到 5 年前
        1
  •  9
  •   T.J. Crowder    5 年前

    你所看到的有几个原因:

    1. async 函数是 同步的 直到第一次 await return ,所以在您的情况下,整个函数在返回之前运行。

    2. 忙碌的等待不是异步的。

    3. test 需要使用 等待 如果要等的话 intense 在继续之前完成。

    4. 把某件事变成承诺并不能使它脱离现实。在大多数javascript环境(包括浏览器)中,唯一的方法是使用 Worker 线程( MDN , Node.js docs -node.js已经 工人 从v10.5开始,虽然它仍然标记为“实验性的”,但是api的主要部分应该是相当稳定的,因为它们是从web worker标准api中提取的)。

    重要的是要记住承诺不会使任何事情变得异步——它们提供了 观察 结果是 已经 异步。

    下面是一个使用 setTimeout 对于帮助您更好地理解这些内容的异步部分:

    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    async function intense(value) {
        console.log("intense(" + value + ") - This is synchronous");
        await delay(100);
        console.log("intense(" + value + ") - This is asynchronous, because it's after `await`");
    }
    
    async function test(){
        console.log("Print 1");
        intense(1);       // <== WITHOUT await
        console.log("Print 2");
        await intense(2); // <== With await
        console.log("Print 3");
    }
    
    test();
    .as-console-wrapper {
      max-height: 100% !important;
    }

    有一个小小的警告:你传递给的处理程序 then , catch finally 总是异步调用,即使调用它们的承诺已经确定。那简直就是 只有 承诺的东西实际上是异步的。

        2
  •  5
  •   Jonas Wilms    5 年前

    所以它会立即返回一个承诺并继续异步工作。

    不,不会的。传递给promise构造函数的回调被立即调用。什么是异步是调用 resolve reject 稍后,以及如何 .then 铁链什么时候会被叫回来。

    但是,它不是异步的,因为代码在另一个线程上运行或被延迟,这不会发生,因为js本身是在一个线程中执行的*。

     console.log(1);
     const promise = new Promise((resolve, reject) => {
      console.log(2); // gets executed immeadiately 
     });
    
     promise.then(() => console.log(4)); // < Promise resolve asynchronously
    console.log(3);
    

    *如果你打算做真正“紧张”的工作,换个思路去做可能会有好处(参见 WebWorker 在浏览器和 child_process.spawn 对于NoDEJs)。

        3
  •  1
  •   Mark    5 年前

    这是一个很容易用javascript造成的误解。你的职能 intense() 阻塞线程。在异步函数中放入某些内容并不能改变javascript中只有一个线程的事实。一旦翻译开始运行 for 循环它将使用一个线程运行它直到结束。在那之前什么都不会发生。

    异步函数不会立即返回,它们运行代码体,直到命中等待并返回承诺。在您的示例中,整个函数将在返回之前运行。

    不能使用这种长时间运行的进程而不阻塞。这就是为什么node会不厌其烦地将i/o访问和计时器之类的东西卸载到另一个线程。

    有关详细信息,请参见此处: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

    如果要异步运行此类长时间运行的代码,则需要派生一个子进程: https://nodejs.org/api/child_process.html