Javascript 后台循环

标签 javascript node.js asynchronous background-task event-loop

假设我们有一个 loop.js 文件:

longLoop().then(res => console.log('loop result processing started'))
console.log('read file started')
require('fs').readFile(__filename, () => console.log('file processing started'))
setTimeout(() => console.log('timer fires'), 500)

async function longLoop () {
  console.log('loop started')
  let res = 0
  for (let i = 0; i < 1e7; i++) {
    res += Math.sin(i) // arbitrary computation heavy operation
    if (i % 1e5 === 0) await null /* solution: await new Promise(resolve => setImmediate(resolve)) */
  }
  console.log('loop finished')
  return res
}

如果运行 (node loop.js) 输出:

loop started
read file started
loop finished
loop result processing started
timer fires
file processing started

当循环在后台运行时,如何重写这段代码来读取和处理文件?

我的解决方案

我想出的是这个:

longLoop().then(res => console.log('loop result processing started'))
console.log('read file started')
require('fs').readFile(__filename, () => console.log('file processing started'))
setTimeout(() => console.log('timer fires'), 500)

async function longLoop () {
  let res = 0
  let from = 0
  let step = 1e5
  let numIterations = 1e7
  function doIterations() {
    //console.log(from)
    return new Promise(resolve => {
      setImmediate(() => { // or setTimeout
        for (let i = from; (i < from + step) && (i < numIterations); i++) {
          res += Math.sin(i)
        }
        resolve()
      })
    })
  }
  console.log('loop started')
  while (from < numIterations) {
    await doIterations()
    from += step
  }
  console.log('loop finished')
  return res
}

确实记录了:

loop started
read file started
file processing started
timer fires
loop finished
loop result processing started

有没有更简单、更简洁的方法来做到这一点?我的解决方案有哪些缺点?

最佳答案

您的代码的第一个版本阻止进一步处理的原因是 await 得到一个立即解决的 promise (值 null 被包装在一个 promise 中,就好像你await Promise.resolve(null))。这意味着 await 之后的代码将在当前“任务”期间恢复:它只是将一个微任务推送到任务队列中,该微任务将在同一任务中使用。您等待的所有其他异步内容都在任务队列中等待,而不是微任务队列。

setTimeout 就是这种情况,readFile 也是如此。它们的回调在任务队列中挂起,因此不会优先于 await 生成的微任务。

所以你需要一种方法让 await 把一些东西放在任务队列而不是微任务队列中。为此,您可以向其提供一个 promise ,该 promise 不会立即解决,而只会在当前任务之后解决。

您可以使用 ....setTimeout 引入延迟:

const slowResolve = val => new Promise(resolve => setTimeout(resolve.bind(null, val), 0));

您可以使用 await 调用该函数。这是一个使用图像加载而不是文件加载的片段,但原理是相同的:

const slowResolve = val => new Promise(resolve => setTimeout(resolve.bind(null, val), 0));

longLoop().then(res => 
    console.log('loop result processing started'))

console.log('read file started')

fs.onload = () => 
    console.log('file processing started');
fs.src = "https://images.pexels.com/photos/34950/pexels-photo.jpg?h=350&auto=compress&cs=tinysrgb";

setTimeout(() => console.log('timer fires'), 500)

async function longLoop () {
  console.log('loop started')
  let res = 0
  for (let i = 0; i < 1e7; i++) {
    res += Math.sin(i) // arbitrary computation heavy operation
    if (i % 1e5 === 0) await slowResolve(i);
  }
  console.log('loop finished')
  return res
}
<img id="fs" src="">

关于Javascript 后台循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45529893/

相关文章:

javascript - 在 jquery 中隐藏一个 div

javascript - 如何通过字符串键获取嵌套 JavaScript 对象属性的值

javascript - 通过 html post 表单登录网站并导航到另一个页面并单击某个元素

c# - 如何等待覆盖异步功能?

javascript - 终极版 react 。 Connect 不更新组件的状态

node.js - 如何在异步写入文件时锁定文件

node.js - 使用 Node 连接到 MongoDB 服务器在本地主机上没有响应

javascript - 向 KeystoneJS 添加自定义管理 UI 功能

JavaScript Promise 绕过解析并继续执行 .then()

android - Kotlin 乐趣过早回归