使用forEach循环在Node.js中全部承诺

时间:2017-06-30 13:54:49

标签: javascript node.js promise es6-promise

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

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`))
        })
      })
    }
  })
}

如何使用promises或Promise All在完成后解决?

3 个答案:

答案 0 :(得分:3)

首先,您需要将每个文件流包装在一个承诺中,该承诺会在流发出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( ...
    ...
  });
});

现在你有一组promises,你可以用Promise.all()包装,并在所有包装的promises已经解决时使用一个处理程序:

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

答案 1 :(得分:2)

在最新版本的Node(8.0.0及更高版本)中,您可以使用新的util.promisify函数来获取承诺。以下是我们如何使用它:

// 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
  })
}

好的,但是,可能还有一些可能令你困惑的事情......

错误怎么样?我一直说你不必担心它们,但 很好地了解它们。基本上,在承诺条款中,当util.promisify' d函数内发生错误时,我们会说该承诺拒绝。被拒绝的承诺的行为与您预期的错误大致相同;他们会抛出错误消息并停止他们所承诺的任何承诺。因此,如果我们的promisifiedReaddir个来电之一拒绝,它将停止整个createFiles功能。

那个copyFile函数怎么样?嗯,我们有两个选择:

  1. 使用其他人的功能。无需重新发明轮子! quickly-copy-file看起来是一个很好的模块(另外,它会返回一个对我们有用的承诺)。

  2. 自己编程。

  3. 实际上,对它进行编程实际上并不是很难,但它需要的不仅仅是使用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)
        })
      })
    }
    

    当然,这种实施并非完美。您可以通过查看其他实现来改进它 - 例如this one在发生错误时销毁读取和写入流,这比我们的方法(它不会这样做)更清晰。最可靠的方式可能与我之前链接的the module一起使用!

    高度建议您观看funfunfunction's video on promises。它解释了承诺如何运作,如何使用Promise.all等等;他解释这整个概念几乎肯定比我更好!

答案 2 :(得分:0)

首先,创建一个返回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(...);