是否需要setTimeout?

时间:2018-05-25 15:19:31

标签: javascript asynchronous async-await settimeout

我有一个async,await和setTimeout()的问题。 我想,我使用异步函数来处理慢进程。所以我尝试了一个大循环。在我的计算机上,运行以下代码需要几秒钟时间:

function slowFunction() {
    return new Promise(resolve => {
        setTimeout(() => {
            for (let i = 0; i < 4000000000; i++) {};
            resolve('Ready at ' + new Date().toLocaleTimeString('de'));
        }, 0);
    });
};


console.log('Start: ' + new Date().toLocaleTimeString('de'));

(async () => {
    console.log('Call slow function.');
    console.log(await slowFunction());
})();

console.log('There is no need to wait for the slow function: ' + new Date().toLocaleTimeString('de'));

输出结果为:

Start: 16:39:20
Call slow function.
There is no need to wait for the slow function: 16:39:20
Ready at 16:39:23

现在的问题是:下一个代码有什么不同:

function slowFunction() {
    return new Promise(resolve => {
        for (let i = 0; i < 4000000000; i++) {};
        resolve('Ready at ' + new Date().toLocaleTimeString('de'));
    });
};

console.log('Start: ' + new Date().toLocaleTimeString('de'));

(async () => {
    console.log('Call slow function.');
    console.log(await slowFunction());
})();

console.log('There is no need to wait for the slow function: ' + new Date().toLocaleTimeString('de'));

输出结果为:

Start: 16:39:20
Call slow function.
There is no need to wait for the slow function: 16:39:23
Ready at 16:39:23

通过第一个示例,它看起来像异步。通过第二个例子,函数等待循环结束。

我是否必须使用setTimeout或者代码中是否有错误或者我错了?在这两种情况下,解决方案都在大循环之后。

async和await的大多数示例都使用了setTimeout,但我认为,它只是为了模拟休息。

提前感谢您的帮助。

最好的问候 帕斯卡

2 个答案:

答案 0 :(得分:5)

TL:DR

Promises和async函数不会将代码卸载到另一个线程。如果要将长时间运行的进程从主线程移开,请在浏览器上查看web workers,然后在Node.js上查看child processes

详细

Promises和async函数(这只是创建和使用promises的语法)不会将您的处理移动到任何其他线程,它仍然发生在您启动该进程的同一个线程上。他们所做的唯一事情是确保异步调用thencatch回调。它们不会使您的代码异步(除了那一点,确保回调异步发生)。

因此,使用setTimeout的第一个块只设置超时,返回一个promise,然后当超时到期时,它会阻止主线程,同时执行慢速运行的进程。这只是在阻塞发生一点点时发生变化,它不会改变阻塞的事实。

你可以在这里看到这个效果,注意当长时间运行的过程发生时计数器暂停的方式:

function slowFunction() {
  return new Promise(resolve => {
    setTimeout(() => {
      const stop = Date.now() + 2000;
      while (Date.now() < stop) {
        // busy wait (obviously, never really do this)
      }
    }, 1000);
  });
};

console.log("before slowFunction");
slowFunction()
  .then(() => {
    console.log("then handler on slowFunction's promise");
  })
  .catch(console.error);
console.log("after slowFunction");

let counter = 0;
const timer = setInterval(() => {
  console.log(++counter);
}, 100);
setTimeout(() => {
  clearInterval(timer);
  console.log("done");
}, 3000);
.as-console-wrapper {
  max-height: 100% !important;
}

你的第二个块没有使用setTimeout只是立即阻塞,因为promise执行器函数(你传递的函数new Promise)立即同步运行,而你没有做任何事情让它异步

你可以在这里看到;计数器立即暂停,而不是稍后:

function slowFunction() {
  return new Promise(resolve => {
    const stop = Date.now() + 2000;
    while (Date.now() < stop) {
      // busy wait (obviously, never really do this)
    }
  });
};

console.log("before slowFunction");
slowFunction()
  .then(() => {
    console.log("then handler on slowFunction's promise");
  })
  .catch(console.error);
console.log("after slowFunction");

let counter = 0;
const timer = setInterval(() => {
  console.log(++counter);
}, 100);
setTimeout(() => {
  clearInterval(timer);
  console.log("done");
}, 3000);
.as-console-wrapper {
  max-height: 100% !important;
}

在长时间运行的代码完成之前,我们甚至没有看到之前的 日志,因为浏览器永远没有机会重新绘制,我们让线程陷入困境。

关于async函数:async函数中的代码开始同步,并且在第一个await(或其他构造,如setTimeout,计划稍后执行的事情)。只有那之后的代码是异步的(因为它必须等待)。

以下是一个证明:

的例子

async function foo() {
  console.log("before await");
  await Promise.resolve();
  console.log("after await");
}

console.log("before foo");
foo()
  .then(() => {
    console.log("then handler on foo's promise");
  })
  .catch(console.error);
console.log("after foo");

这是输出:

before foo
before await
after foo
after await
then handler on foo's promise

注意在等待之前之后 之前的;它与foo的调用同步。但是等待之后的直到稍后才会发生(因为await Promise.resolve()必须使其后面的代码异步发生;它是then的语法糖,它承诺不会调用它即使承诺已经解决,也会同步处理。)

答案 1 :(得分:0)

不同之处在于这是完全同步的代码:

return new Promise(resolve => {
    for (let i = 0; i < 4000000000; i++) {};
    resolve('Ready at ' + new Date().toLocaleTimeString('de'));
});

此语句将阻止JavaScript线程并强制它等待所有这40亿次迭代发生。 然后它将继续下一个语句。由于console.log在此之后执行,因此在该循环结束之前不会执行。

这就是你看到差异的原因。