[英]Promise All in Node.js with a forEach loop
I have a function that reads a directory and copies and creates a new file within that directory. 我有一个读取目录并复制并在该目录中创建新文件的功能。
function createFiles (countryCode) {
fs.readdir('./app/data', (err, directories) => {
if (err) {
console.log(err)
} else {
directories.forEach((directory) => {
fs.readdir(`./app/data/${directory}`, (err, files) => {
if (err) console.log(err)
console.log(`Creating ${countryCode}.yml for ${directory}`)
fs.createReadStream(`./app/data/${directory}/en.yml`).pipe(fs.createWriteStream(`./app/data/${directory}/${countryCode}.yml`))
})
})
}
})
}
How do I do this using promises or Promise All to resolve when it's complete? 完成后,我该如何使用Promise或Promise All来解决?
First, you need to wrap each file stream in a promise that resolves when the stream emits the finish
event: 首先,您需要将每个文件流包装在一个Promise中,该Promise会在该文件流发出finish
事件时进行解析:
new Promise((resolve, reject) => {
fs.createReadStream(`./app/data/${directory}/en.yml`).pipe(
fs.createWriteStream(`./app/data/${directory}/${countryCode}.yml`)
).on('finish', resolve);
});
The you need to collect these promises in an array. 您需要以数组形式收集这些承诺。 This is done by using map()
instead of forEach()
and returning the promise: 这是通过使用map()
而不是forEach()
并返回promise来完成的:
var promises = directories.map((directory) => {
...
return new Promise((resolve, reject) => {
fs.createReadStream( ...
...
});
});
Now you have a collection of promises that you can wrap with Promise.all()
and use with a handler when all the wrapped promises have resolved: 现在,您有了一个Promise.all()
集合,可以用Promise.all()
包装它们,并在所有包装的Promise.all()
都解决后与处理程序一起使用:
Promise.all(promises).then(completeFunction);
In recent versions of Node (8.0.0 and later), there's a new util.promisify
function you can use to get a promise. 在最新版本的Node(8.0.0及更高版本)中,有一个新的util.promisify
函数可用于获得承诺。 Here's how we might use it: 这是我们可能使用它的方式:
// Of course we'll need to require important modules before doing anything
// else.
const util = require('util')
const fs = require('fs')
// We use the "promisify" function to make calling promisifiedReaddir
// return a promise.
const promisifiedReaddir = util.promisify(fs.readdir)
// (You don't need to name the variable util.promisify promisifiedXYZ -
// you could just do `const readdir = util.promisify(fs.readdir)` - but
// I call it promisifiedReaddir here for clarity.
function createFiles(countryCode) {
// Since we're using our promisified readdir function, we'll be storing
// a Promise inside of the readdirPromise variable..
const readdirPromise = promisifiedReaddir('./app/data')
// ..then we can make something happen when the promise finishes (i.e.
// when we get the list of directories) by using .then():
return readdirPromise.then(directories => {
// (Note that we only get the parameter `directories` here, with no `err`.
// That's because promises have their own way of dealing with errors;
// try looking up on "promise rejection" and "promise error catching".)
// We can't use a forEach loop here, because forEach doesn't know how to
// deal with promises. Instead we'll use a Promise.all with an array of
// promises.
// Using the .map() method is a great way to turn our list of directories
// into a list of promises; read up on "array map" if you aren't sure how
// it works.
const promises = directory.map(directory => {
// Since we want an array of promises, we'll need to `return` a promise
// here. We'll use our promisifiedReaddir function for that; it already
// returns a promise, conveniently.
return promisifiedReaddir(`./app/data/${directory}`).then(files => {
// (For now, let's pretend we have a "copy file" function that returns
// a promise. We'll actually make that function later!)
return copyFile(`./app/data/${directory}/en.yml`, `./app/data/${directory}/${countryCode}.yml`)
})
})
// Now that we've got our array of promises, we actually need to turn them
// into ONE promise, that completes when all of its "children" promises
// are completed. Luckily there's a function in JavaScript that's made to
// do just that - Promise.all:
const allPromise = Promies.all(promises)
// Now if we do a .then() on allPromise, the function we passed to .then()
// would only be called when ALL promises are finished. (The function
// would get an array of all the values in `promises` in order, but since
// we're just copying files, those values are irrelevant. And again, don't
// worry about errors!)
// Since we've made our allPromise which does what we want, we can return
// it, and we're done:
return allPromise
})
}
Okay, but, there's probably still a few things that might be puzzling you.. 好的,但是,可能仍然有些事情让您感到困惑。
What about errors? 错误呢? I kept saying that you don't need to worry about them, but it is good to know a little about them. 我一直在说,您不必担心它们,但是对它们有所了解是一件好事。 Basically, in promise-terms, when an error happens inside of a util.promisify
'd function, we say that that promise rejects . 基本上,在诺言条款中,当在util.promisify
函数内部发生错误时,我们说诺言会被拒绝 。 Rejected promises behave mostly the same way you'd expect errors to; 被拒绝的诺言的行为基本上与您期望错误的方式相同; they throw an error message and stop whatever promise they're in. So if one of our promisifiedReaddir
calls rejects, it'll stop the whole createFiles
function. 他们会抛出错误消息并停止所有的承诺。因此,如果我们的promisifiedReaddir
调用之一拒绝,它将停止整个createFiles
函数。
What about that copyFile
function? 那那个copyFile
函数呢? Well, we have two options: 好吧,我们有两个选择:
Use somebody else's function. 使用别人的功能。 No need to re-invent the wheel! 无需重新发明轮子! quickly-copy-file
looks to be a good module (plus, it returns a promise, which is useful for us). quickly-copy-file
看起来是一个不错的模块(此外,它会返回一个promise,这对我们很有用)。
Program it ourselves. 自己编程。
Programming it ourselves isn't too hard, actually, but it takes a little bit more than simply using util.promisify
: 实际上,我们自己编写它并不难,但是它比简单地使用util.promisify
花费了更多util.promisify
:
function copyFile(from, to) {
// Hmm.. we want to copy a file. We already know how to do that in normal
// JavaScript - we'd just use a createReadStream and pipe that into a
// createWriteStream. But we need to return a promise for our code to work
// like we want it to.
// This means we'll have to make our own hand-made promise. Thankfully,
// that's not actually too difficult..
return new Promise((resolve, reject) => {
// Yikes! What's THIS code mean?
// Well, it literally says we're returning a new Promise object, with a
// function given to it as an argument. This function takes two arguments
// of its own: "resolve" and "reject". We'll look at them separately
// (but maybe you can guess what they mean already!).
// We do still need to create our read and write streams like we always do
// when copying files:
const readStream = fs.createReadStream(from)
const writeStream = fs.createWriteStream(to)
// And we need to pipe the read stream into the write stream (again, as
// usual):
readStream.pipe(writeStream)
// ..But now we need to figure out how to tell the promise when we're done
// copying the files.
// Well, we'll start by doing *something* when the pipe operation is
// finished. That's simple enough; we'll just set up an event listener:
writeStream.on('close', () => {
// Remember the "resolve" and "reject" functions we got earlier? Well, we
// can use them to tell the promise when we're done. So we'll do that here:
resolve()
})
// Okay, but what about errors? What if, for some reason, the pipe fails?
// That's simple enough to deal with too, if you know how. Remember how we
// learned a little on rejected promises, earlier? Since we're making
// our own Promise object, we'll need to create that rejection ourself
// (if anything goes wrong).
writeStream.on('error', err => {
// We'll use the "reject" argument we were given to show that something
// inside the promise failed. We can specify what that something is by
// passing the error object (which we get passed to our event listener,
// as usual).
reject(err)
})
// ..And we'll do the same in case our read stream fails, just in case:
readStream.on('error', err => {
reject(err)
})
// And now we're done! We've created our own hand-made promise-returning
// function, which we can use in our `createFiles` function that we wrote
// earlier.
})
}
..And here's all the finished code, so that you can review it yourself: ..这是所有完成的代码,因此您可以自己查看它:
const util = require('util')
const fs = require('fs')
const promisifiedReaddir = util.promisify(fs.readdir)
function createFiles(countryCode) {
const readdirPromise = promisifiedReaddir('./app/data')
return readdirPromise.then(directories => {
const promises = directory.map(directory => {
return promisifiedReaddir(`./app/data/${directory}`).then(files => {
return copyFile(`./app/data/${directory}/en.yml`, `./app/data/${directory}/${countryCode}.yml`)
})
})
const allPromise = Promies.all(promises)
return allPromise
})
}
function copyFile(from, to) {
return new Promise((resolve, reject) => {
const readStream = fs.createReadStream(from)
const writeStream = fs.createWriteStream(to)
readStream.pipe(writeStream)
writeStream.on('close', () => {
resolve()
})
writeStream.on('error', err => {
reject(err)
})
readStream.on('error', err => {
reject(err)
})
})
}
Of course, this implementation isn't perfect . 当然,这种实现并不完美 。 You could improve it by looking at other implementations - for example this one destroys the read and write streams when an error occurs, which is a bit cleaner than our method (which doesn't do that). 你可以通过看其他实现改进-比如这一个破坏读,当出现错误时,比我们的方法清洁有点写入流(不这样做)。 The most reliable way would probably to go with the module I linked earlier! 最可靠的方法可能是与我先前链接的模块配合使用 !
I highly recommend you watch funfunfunction's video on promises . 我强烈建议您按承诺观看funfunfunction的视频 。 It explains how promises work in general, how to use Promise.all
, and more; 它说明了Promise.all
一般如何工作,如何使用Promise.all
等。 and he's almost certainly better at explaining this whole concept than I am! 而且他几乎可以肯定比我更擅长于解释整个概念!
First, create a function that returns a promise: 首先,创建一个返回诺言的函数:
function processDirectory(directory) {
return new Promise((resolve, reject) => {
fs.readdir(`./app/data/${directory}`, (err, files) => {
if (err) reject(err);
console.log(`Creating ${countryCode}.yml for ${directory}`);
fs.createReadStream(`./app/data/${directory}/en.yml`)
.pipe(fs.createWriteStream(`./app/data/${directory}/${countryCode}.yml`))
.on('finish', resolve);
});
});
}
Then use Promise.all: 然后使用Promise.all:
Promise.all(directories.map(processDirectory))
.then(...)
.catch(...);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.