Javascript Promise All in Node.js和forEach循环

Javascript Promise All in Node.js和forEach循环,javascript,node.js,promise,es6-promise,Javascript,Node.js,Promise,Es6 Promise,我有一个函数,可以读取一个目录,并在该目录中复制和创建一个新文件 函数创建文件(countryCode){ fs.readdir('./app/data',(错误,目录)=>{ 如果(错误){ console.log(错误) }否则{ 目录。forEach((目录)=>{ fs.readdir(`./app/data/${directory}`,(err,files)=>{ if(err)console.log(err) log(`Creating${countryCode}.yml for${

我有一个函数,可以读取一个目录,并在该目录中复制和创建一个新文件

函数创建文件(countryCode){
fs.readdir('./app/data',(错误,目录)=>{
如果(错误){
console.log(错误)
}否则{
目录。forEach((目录)=>{
fs.readdir(`./app/data/${directory}`,(err,files)=>{
if(err)console.log(err)
log(`Creating${countryCode}.yml for${directory}`)
fs.createReadStream(`./app/data/${directory}/en.yml`).pipe(fs.createWriteStream(`./app/data/${directory}/${countryCode}.yml`)
})
})
}
})
}

如何使用Promission或Promise All在完成时解决此问题?

首先,创建一个返回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);
    });
  });
}
然后使用Promise.all:

Promise.all(directories.map(processDirectory))
  .then(...)
  .catch(...);

首先,创建一个返回承诺的函数:

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);
    });
  });
}
然后使用Promise.all:

Promise.all(directories.map(processDirectory))
  .then(...)
  .catch(...);

首先,您需要将每个文件流包装在一个承诺中,该承诺在流发出
finish
事件时解析:

new Promise((resolve, reject) => {
  fs.createReadStream(`./app/data/${directory}/en.yml`).pipe(
    fs.createWriteStream(`./app/data/${directory}/${countryCode}.yml`)
  ).on('finish', resolve);
});
您需要在一个数组中收集这些承诺。这是通过使用
map()
而不是
forEach()
并返回承诺来实现的:

var promises = directories.map((directory) => {
  ...
  return new Promise((resolve, reject) => {
    fs.createReadStream( ...
    ...
  });
});
现在,您有了一个承诺集合,可以使用
Promise.all()
包装这些承诺,并在所有包装的承诺都已解决时与处理程序一起使用:

Promise.all(promises).then(completeFunction);

首先,您需要将每个文件流包装在一个承诺中,该承诺在流发出
finish
事件时解析:

new Promise((resolve, reject) => {
  fs.createReadStream(`./app/data/${directory}/en.yml`).pipe(
    fs.createWriteStream(`./app/data/${directory}/${countryCode}.yml`)
  ).on('finish', resolve);
});
您需要在一个数组中收集这些承诺。这是通过使用
map()
而不是
forEach()
并返回承诺来实现的:

var promises = directories.map((directory) => {
  ...
  return new Promise((resolve, reject) => {
    fs.createReadStream( ...
    ...
  });
});
现在,您有了一个承诺集合,可以使用
Promise.all()
包装这些承诺,并在所有包装的承诺都已解决时与处理程序一起使用:

Promise.all(promises).then(completeFunction);

在Node的最新版本(8.0.0及更高版本)中,有一个新函数可用于获得承诺。下面是我们可以如何使用它:

// 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
  })
}
好吧,但是,可能还有一些事情让你困惑

关于错误呢?我一直说你不必担心它们,但是了解一点它们是很好的。基本上,在promise术语中,当
util.promisify
'd函数内部发生错误时,我们称该promise拒绝。被拒绝的承诺的行为与你预期的错误大致相同;他们抛出一条错误信息,停止他们的任何承诺。因此,如果我们的
promisifiedireddir
调用之一被拒绝,它将停止整个
createFiles
函数

copyFile
功能呢?
我们有两个选择:

  • 使用别人的功能。没有必要重新发明轮子!看起来是一个很好的模块(另外,它返回一个承诺,这对我们很有用)

  • 自己编程

  • 事实上,自己编程并不难,但这比简单地使用
    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.
      })
    }
    
    …这是所有完成的代码,您可以自己查看:

    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)
        })
      })
    }
    
    当然,这种实现并不完美。您可以通过查看其他实现来改进它—例如,在发生错误时销毁读写流,这比我们的方法(它不这样做)要干净一些。最可靠的方法可能是使用我之前链接的



    我强烈推荐你看。它解释了承诺通常是如何工作的,如何使用
    Promise.all
    ,等等;他几乎肯定比我更善于解释整个概念

    在Node的最新版本(8.0.0及更高版本)中,有一个新函数可用于获得承诺。下面是我们可以如何使用它:

    // 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
      })
    }
    
    好吧,但是,可能还有一些事情让你困惑

    关于错误呢?我一直说你不必担心它们,但是了解一点它们是很好的。基本上,在promise术语中,当
    util.promisify
    'd函数内部发生错误时,我们称该promise拒绝。被拒绝的承诺的行为与你预期的错误大致相同;他们抛出一条错误信息,停止他们的任何承诺。因此,如果我们的
    promisifiedireddir
    调用之一被拒绝,它将停止整个
    createFiles
    函数

    copyFile
    功能呢?
    我们有两个选择:

  • 使用别人的功能。没有必要重新发明轮子!看起来是一个很好的模块(另外,它返回一个承诺,这对我们很有用)

  • 自己编程

  • 事实上,自己编程并不难,但这比简单地使用
    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.
      })
    }
    
    …这是所有完成的代码,您可以自己查看:

    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)
        })
      })
    }
    
    当然,这种实现并不完美。您可以通过查看其他实现来改进它—例如,在发生错误时销毁读写流,这比我们的方法(它不这样做)要干净一些。最可靠的方法可能是使用我之前链接的



    我强烈推荐你看。它解释了承诺通常是如何工作的,如何使用
    Promise.all
    ,等等;他几乎肯定比我更善于解释整个概念

    您需要包装
    fs
    方法,使它们承诺返回。或者使用类似的方法:看看这个递归目录复制的示例:您需要包装
    fs
    方法,使它们返回。或者使用类似的东西:看看这个递归目录复制的例子:这是一个输入错误,我会写(目录)=>processDirectory(目录),但是,是的,它可以通过这种方式简化(现在编辑)。谢谢当写入新文件完成时,这不会解决,它会在流开始写入新文件时解决