在需要回调(例如 Array.map)的循环中使用 async/await 时,到底是什么影响了 fs.writeFile 的行为?
What exactly is affecting how fs.writeFile behaves when using async/await in a loop requiring a callback such as Array.map?
这个问题似乎与字符串长度有关,但我发现在代码的某些地方包含 console.log()
时会发生此问题。需要明确的是,只有在需要回调的循环中使用 fs.writeFile
时才会出现此问题,例如 Array.map
.
当我使用 for
循环,没有问题。预期的结果是,到循环结束时,写入的文件仅包含数组最后一个元素的数据。一切都很好。您可以在 here on repl.it 上查看。这是代码:
const fs = require('fs')
const writeFile = (uri, data, options) => new Promise((resolve, reject) => {
fs.writeFile(uri, data, (err) => {
if (err) {
return reject(`Error writing file: ${uri} --> ${err}`)
}
resolve(`Successfully wrote file: ${uri} --> with data: ${data}`)
})
})
const asyncWriteFile = (uri, data) => {
return writeFile(uri, data).then( res => console.log(res)).catch(e => console.log(e))
}
const asyncWriteFileLoop = async (uri, seed) => {
for (let i = 0; i < dataArr.length; i++) {
await asyncWriteFile(uri, seed[i])
}
}
const uri = 'test-write-dump.txt'
const dataArr = [
'this is string long enough to cause an issue when using Array.map but not in a for loop',[1,2,3,4],1600,'foobar',['foo','bar','baz']
]
// works as expected, output in file is foo,bar,baz
asyncWriteFileLoop(uri, dataArr)
问题来了:当我尝试使用 Promise.all
将 for
循环替换为 Array.map
时。该文件是附加的,而不是被覆盖的,并且以一种看似奇怪且不可预测的顺序。我意识到这是连续调用 fs.writeFile
过快的本质。然而,我期待一系列的承诺能够一个接一个地解决,从而绕过这个问题,但事实似乎并非如此。您可以在 here on repl.it 上查看。这是有问题的代码:
const fs = require('fs')
const writeFile = (uri, data, options) => new Promise((resolve, reject) => {
fs.writeFile(uri, data, (err) => {
if (err) {
return reject(`Error writing file: ${uri} --> ${err}`)
}
resolve(`Successfully wrote file: ${uri} --> with data: ${data}`)
})
})
const appendFile = (uri, data, options) => new Promise((resolve, reject) => {
fs.appendFile(uri, data, (err) => {
if (err) {
return reject(`Error appending file: ${uri} --> ${err}`)
}
resolve(`Successfully appended file: ${uri} --> with data: ${data}`)
})
})
asyncWriteFileMap = async (uri, seed) => {
const promises = seed.map(async data => {
await writeFile(uri, data)
.then(res => console.log(res))
.catch(e => console.log(e))
})
const result = await Promise.all(promises)
}
const uri = 'test-write-dump.txt'
const dataArr = [
'This is a string long enough to cause an issue, change this to a single character and the expected result almost always occurs',[1,2,3,4],1600,'foobar',['foo','bar','baz']
]
// Seems like it doesnt work with long strings, writes a junk file
asyncWriteFileMap(uri, dataArr)
我真的很想知道确切地 是什么导致了这种行为,为什么 for
循环工作而 Array.map
不工作。
.map()
不是 promise-aware。它不会注意你的回调 returns 的承诺。相反,它只是将 promise 放在结果数组中并继续其循环。
因此,发生的情况是 .map()
立即启动所有 writeFileAsync()
调用,然后在稍后的某个时间每个承诺完成。您在回调中使用的 await
不会暂停 .map()
迭代 - 事实上,它根本没有帮助您。
另一方面,for
循环是 promise-aware 并将暂停 await
.
的循环
因此,for
循环被 await
暂停,.map()
循环没有。
如果您没有意识到,当 async
函数(例如您的 .map()
回调)遇到它的第一个 await
时,async
函数会立即 returns一个承诺。它暂停进一步的函数执行,直到 await
看到已解决的承诺,但函数本身 returns 调用代码的承诺和执行继续。这就是 .map()
继续其其余迭代而不等待 await
.
的原因
此外,请记住 Promise.all()
不会“运行”任何东西。异步操作已经 运行ning 并且 Promise.all()
只是给出了一组承诺来监视和收集结果。所以,说 Promise.all()
运行 是并行的,而不是特定的顺序是不正确的。在你的例子中是 .map()
这样做的。
Promise.all()
通常用于多个 promise-based 异步操作并行 运行 的情况,但这些操作是由其他东西启动的。 Promise.all()
只是监控完成和收集结果。
这个问题似乎与字符串长度有关,但我发现在代码的某些地方包含 console.log()
时会发生此问题。需要明确的是,只有在需要回调的循环中使用 fs.writeFile
时才会出现此问题,例如 Array.map
.
当我使用 for
循环,没有问题。预期的结果是,到循环结束时,写入的文件仅包含数组最后一个元素的数据。一切都很好。您可以在 here on repl.it 上查看。这是代码:
const fs = require('fs')
const writeFile = (uri, data, options) => new Promise((resolve, reject) => {
fs.writeFile(uri, data, (err) => {
if (err) {
return reject(`Error writing file: ${uri} --> ${err}`)
}
resolve(`Successfully wrote file: ${uri} --> with data: ${data}`)
})
})
const asyncWriteFile = (uri, data) => {
return writeFile(uri, data).then( res => console.log(res)).catch(e => console.log(e))
}
const asyncWriteFileLoop = async (uri, seed) => {
for (let i = 0; i < dataArr.length; i++) {
await asyncWriteFile(uri, seed[i])
}
}
const uri = 'test-write-dump.txt'
const dataArr = [
'this is string long enough to cause an issue when using Array.map but not in a for loop',[1,2,3,4],1600,'foobar',['foo','bar','baz']
]
// works as expected, output in file is foo,bar,baz
asyncWriteFileLoop(uri, dataArr)
问题来了:当我尝试使用 Promise.all
将 for
循环替换为 Array.map
时。该文件是附加的,而不是被覆盖的,并且以一种看似奇怪且不可预测的顺序。我意识到这是连续调用 fs.writeFile
过快的本质。然而,我期待一系列的承诺能够一个接一个地解决,从而绕过这个问题,但事实似乎并非如此。您可以在 here on repl.it 上查看。这是有问题的代码:
const fs = require('fs')
const writeFile = (uri, data, options) => new Promise((resolve, reject) => {
fs.writeFile(uri, data, (err) => {
if (err) {
return reject(`Error writing file: ${uri} --> ${err}`)
}
resolve(`Successfully wrote file: ${uri} --> with data: ${data}`)
})
})
const appendFile = (uri, data, options) => new Promise((resolve, reject) => {
fs.appendFile(uri, data, (err) => {
if (err) {
return reject(`Error appending file: ${uri} --> ${err}`)
}
resolve(`Successfully appended file: ${uri} --> with data: ${data}`)
})
})
asyncWriteFileMap = async (uri, seed) => {
const promises = seed.map(async data => {
await writeFile(uri, data)
.then(res => console.log(res))
.catch(e => console.log(e))
})
const result = await Promise.all(promises)
}
const uri = 'test-write-dump.txt'
const dataArr = [
'This is a string long enough to cause an issue, change this to a single character and the expected result almost always occurs',[1,2,3,4],1600,'foobar',['foo','bar','baz']
]
// Seems like it doesnt work with long strings, writes a junk file
asyncWriteFileMap(uri, dataArr)
我真的很想知道确切地 是什么导致了这种行为,为什么 for
循环工作而 Array.map
不工作。
.map()
不是 promise-aware。它不会注意你的回调 returns 的承诺。相反,它只是将 promise 放在结果数组中并继续其循环。
因此,发生的情况是 .map()
立即启动所有 writeFileAsync()
调用,然后在稍后的某个时间每个承诺完成。您在回调中使用的 await
不会暂停 .map()
迭代 - 事实上,它根本没有帮助您。
另一方面,for
循环是 promise-aware 并将暂停 await
.
因此,for
循环被 await
暂停,.map()
循环没有。
如果您没有意识到,当 async
函数(例如您的 .map()
回调)遇到它的第一个 await
时,async
函数会立即 returns一个承诺。它暂停进一步的函数执行,直到 await
看到已解决的承诺,但函数本身 returns 调用代码的承诺和执行继续。这就是 .map()
继续其其余迭代而不等待 await
.
此外,请记住 Promise.all()
不会“运行”任何东西。异步操作已经 运行ning 并且 Promise.all()
只是给出了一组承诺来监视和收集结果。所以,说 Promise.all()
运行 是并行的,而不是特定的顺序是不正确的。在你的例子中是 .map()
这样做的。
Promise.all()
通常用于多个 promise-based 异步操作并行 运行 的情况,但这些操作是由其他东西启动的。 Promise.all()
只是监控完成和收集结果。