假设我们有一个 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/