2017-06-30 71 views
0

我有一個讀取目錄並在該目錄中創建新文件的函數。使用forEach循環承諾Node.js中的所有

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

我該如何使用承諾或承諾完成所有工作?

+0

您將需要包裝'fs'方法,以使它們保證返回。或者使用像這樣的:https://github.com/normalize/mz –

+0

看看這個遞歸目錄副本的例子:https://github.com/amaksr/nsynjs/blob/master/examples/node-copy- files/index.js – amaksr

回答

0

首先,創建一個返回一個承諾的功能:

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(...); 
+0

這是一個錯字,我會寫(目錄)=> processDirectory(目錄),但是,它可以用這種方式簡化(現在編輯)。謝謝! –

+0

當寫入新文件完成時,這不會解決,它會在流開始寫入新文件時解決問題 –

+0

是不是createWriteStream同步? –

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(... 
    ... 
    }); 
}); 

現在你有,你可以用Promise.all()包裹,並用處理器使用承諾的集合,當所有包裹的承諾已經解決:

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

在最近的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的函數內部時,我們說那個承諾拒絕。被拒絕的承諾的表現大體上與您預期錯誤的方式相同;他們會拋出錯誤信息並停止他們所承諾的任何承諾。因此,如果我們的promisifiedReaddir調用中有一個拒絕,它將停止整個createFiles函數。

那麼copyFile函數呢?那麼,我們有兩個選擇:

  1. 使用別人的功能。無需重新發明輪子! quickly-copy-file看起來是一個很好的模塊(加上它會返回一個承諾,這對我們很有用)。

  2. 自己編程。

編程它自己是不是太狠了,居然,但它需要比單純使用util.promisify多一點點:

​​

..而這裏的所有完成的代碼,這樣就可以自己審查:

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等;而且他幾乎可以肯定比我更好地解釋這個整體概念!