2017-08-15 39 views
0

我们长期的Python和PHP编码器具有整齐的同步代码位(示例如下)。大部分功能都有异步对应。我们真的想'获得'Javascript和Node的力量,并且认为这是异步node.js加快速度并吹嘘的理想情况。重构同步代码以释放node.js异步性的力量

什么是重构以下利用异步节点的教科书方式? Async/awaitpromise.all?怎么样? (使用节点8.4.0的向后兼容性不是一个问题。)

var fs = require('fs'); 

// This could list over 10,000 files of various size 
const fileList = ['file1', 'file2', 'file3']; 

const getCdate = file => fs.statSync(file).ctime; // Has async method 

const getFSize = file => fs.statSync(file).size; // Has async method 

// Can be async through file streams (see resources below) 
const getMd5 = (file) => { 
    let fileData = new Buffer(0); 
    fileData = fs.readFileSync(file); 
    const hash = crypto.createHash('md5'); 
    hash.update(fileData); 
    return hash.digest('hex'); 
}; 

let filesObj = fileList.map(file => [file, { 
    datetime: getCdate(file), 
    filesize: getFSize(file), 
    md5hash: getMd5(file), 
}]); 

console.log(filesObj); 

注:

  • 我们需要保持功能的模块化和可重复使用。
  • 有比更多的功能
  • 大多数函数可以被重写为异步,有些不能。
  • 理想情况下,我们需要保持原来的订单fileList
  • 理想情况下,我们希望使用最新的Node和JS功能 - 不依赖于外部模块。为获得MD5异步

什锦文件流的方法:

+0

没有解释的downvote是无用的。请帮助改善问题。 – Trees4theForest

+1

我希望downvote弹出一个框,要求用户输入至少一个简短的理由。 – Chev

回答

0

我会让getCDategetFSize,并且getMd5所有的异步和promisified然后包他们在另一个异步hronous promise-returning函数,这里叫做statFile

function statFile(file) { 
    return Promise.all([ 
     getCDate(file), 
     getFSize(file), 
     getMd5(file) 
    ]).then((datetime, filesize, md5hash) => ({datetime, filesize, md5hash})) 
    .catch(/*handle error*/); 
} 

然后你可以改变你的映射功能

const promises = fileList.map(statFile); 

然后,它使用简单,Promise.all:

Promise.all(promises) 
    .then(filesObj => /*do something*/) 
    .catch(err => /*handle error*/) 

这留下的东西模块化,不需要异步/ AWAIT ,允许您将额外的功能插入statFile,并保留您的文件顺序。

+1

要从箭头函数返回对象,您需要将其包装在括号中,以便不会将括号打开代码块。 '(datetime,filesize,md5hash)=>({datetime,filesize,md5hash});':) – Chev

+1

@Chev谢谢,我不知道这个问题的答案。 –

1

您可以通过多种不同的方式异步处理此代码。您可以使用节点async库更优雅地处理所有回调。如果你不想潜入承诺那么这就是“简单”的选择。我容易引用,因为如果你足够了解它们,承诺实际上更容易。异步库很有帮助,但它仍然存在很多错误传播的方式,并且有很多样板代码需要将所有的调用包装进去。

更好的方法是使用promise 。异步/等待仍然很新。没有像Bable或Typescript这样的预处理器,甚至在节点7中都不支持(不确定节点8)。而且,async/await无论如何都会使用承诺。

这是我会怎么使用承诺做到这一点,甚至包括了最大性能文件统计缓存:

const fs = require('fs'); 
const crypto = require('crypto'); 
const Promise = require('bluebird'); 
const fileList = ['file1', 'file2', 'file3']; 

// Use Bluebird's Promise.promisifyAll utility to turn all of fs' 
// async functions into promise returning versions of them. 
// The new promise-enabled methods will have the same name but with 
// a suffix of "Async". Ex: fs.stat will be fs.statAsync. 
Promise.promisifyAll(fs); 

// Create a cache to store the file if we're planning to get multiple 
// stats from it. 
let cache = { 
    fileName: null, 
    fileStats: null 
}; 
const getFileStats = (fileName, prop) => { 
    if (cache.fileName === fileName) { 
    return cache.fileStats[prop]; 
    } 
    // Return a promise that eventually resolves to the data we're after 
    // but also stores fileStats in our cache for future calls. 
    return fs.statAsync(fileName).then(fileStats => { 
    cache.fileName = fileName; 
    cache.fileStats = fileStats; 
    return fileStats[prop]; 
    }) 
}; 

const getMd5Hash = file => { 
    // Return a promise that eventually resolves to the hash we're after. 
    return fs.readFileAsync(file).then(fileData => { 
    const hash = crypto.createHash('md5'); 
    hash.update(fileData); 
    return hash.digest('hex'); 
    }); 
}; 

// Create a promise that immediately resolves with our fileList array. 
// Use Bluebird's Promise.map utility. Works very similar to Array.map 
// except it expects all array entries to be promises that will 
// eventually be resolved to the data we want. 
let results = Promise.resolve(fileList).map(fileName => { 
    return Promise.all([ 

    // This first gets a promise that starts resolving file stats 
    // asynchronously. When the promise resolves it will store file 
    // stats in a cache and then return the stats value we're after. 
    // Note that the final return is not a promise, but returning raw 
    // values from promise handlers implicitly does 
    // Promise.resolve(rawValue) 
    getFileStats(fileName, 'ctime'), 

    // This one will not return a promise. It will see cached file 
    // stats for our file and return the stats value from the cache 
    // instead. Since it's being returned into a Promise.all, it will 
    // be implicitly wrapped in Promise.resolve(rawValue) to fit the 
    // promise paradigm. 
    getFileStats(fileName, 'size'), 

    // First returns a promise that begins resolving the file data for 
    // our file. A promise handler in the function will then perform 
    // the operations we need to do on the file data in order to get 
    // the hash. The raw hash value is returned in the end and 
    // implicitly wrapped in Promise.resolve as well. 
    getMd5(file) 
    ]) 
    // .spread is a bluebird shortcut that replaces .then. If the value 
    // being resolved is an array (which it is because Promise.all will 
    // resolve an array containing the results in the same order as we 
    // listed the calls in the input array) then .spread will spread the 
    // values in that array out and pass them in as individual function 
    // parameters. 
    .spread((dateTime, fileSize, md5Hash) => [file, { dateTime, fileSize, md5Hash }]); 
}).catch(error => { 
    // Any errors returned by any of the Async functions in this promise 
    // chain will be propagated here. 
    console.log(error); 
}); 

这里是再次的代码,但没有注释,使其更容易查看:

const fs = require('fs'); 
const crypto = require('crypto'); 
const Promise = require('bluebird'); 
const fileList = ['file1', 'file2', 'file3']; 

Promise.promisifyAll(fs); 

let cache = { 
    fileName: null, 
    fileStats: null 
}; 
const getFileStats = (fileName, prop) => { 
    if (cache.fileName === fileName) { 
    return cache.fileStats[prop]; 
    } 
    return fs.statAsync(fileName).then(fileStats => { 
    cache.fileName = fileName; 
    cache.fileStats = fileStats; 
    return fileStats[prop]; 
    }) 
}; 

const getMd5Hash = file => { 
    return fs.readFileAsync(file).then(fileData => { 
    const hash = crypto.createHash('md5'); 
    hash.update(fileData); 
    return hash.digest('hex'); 
    }); 
}; 

let results = Promise.resolve(fileList).map(fileName => { 
    return Promise.all([ 
    getFileStats(fileName, 'ctime'), 
    getFileStats(fileName, 'size'), 
    getMd5(file) 
    ]).spread((dateTime, fileSize, md5Hash) => [file, { dateTime, fileSize, md5Hash }]); 
}).catch(console.log); 

最终结果将是一个数组喜欢它应该有希望匹配您的原码的结果,而应执行的基准好得多:

[ 
    ['file1', { dateTime: 'data here', fileSize: 'data here', md5Hash: 'data here' }], 
    ['file2', { dateTime: 'data here', fileSize: 'data here', md5Hash: 'data here' }], 
    ['file3', { dateTime: 'data here', fileSize: 'data here', md5Hash: 'data here' }] 
] 

任何错别字提前道歉。没有时间或能力去实际运行这些。尽管我看了很多。


在发现async/await在7.6节点后,我决定昨晚玩一下它。对于不需要并行执行的递归异步任务,或者您希望可以同步写入的嵌套异步任务,似乎最为有用。对于你在这里需要的东西,没有任何令人兴奋的方式来使用异步/等待,我可以看到,但有几个地方的代码会更干净地阅读。这是代码,但有一些异步/等待的便利。

const fs = require('fs'); 
const crypto = require('crypto'); 
const Promise = require('bluebird'); 
const fileList = ['file1', 'file2', 'file3']; 

Promise.promisifyAll(fs); 

let cache = { 
    fileName: null, 
    fileStats: null 
}; 
async function getFileStats (fileName, prop) { 
    if (cache.fileName === fileName) { 
    return cache.fileStats[prop]; 
    } 
    let fileStats = await fs.stat(fileName); 
    cache.fileName = fileName; 
    cache.fileStats = fileStats; 
    return fileStats[prop]; 
}; 

async function getMd5Hash (file) { 
    let fileData = await fs.readFileAsync(file); 
    const hash = crypto.createHash('md5'); 
    hash.update(fileData); 
    return hash.digest('hex'); 
}; 

let results = Promise.resolve(fileList).map(fileName => { 
    return Promise.all([ 
    getFileStats(fileName, 'ctime'), 
    getFileStats(fileName, 'size'), 
    getMd5(file) 
    ]).spread((dateTime, fileSize, md5Hash) => [file, { dateTime, fileSize, md5Hash }]); 
}).catch(console.log); 
+0

太棒了。感谢您的想法。 'async' /'await'是节点的一部分,因为我相信7.6,诺言也是标准的。不要以为我们需要蓝鸟(不知道'promisfy'),我们宁愿尽可能地做'本地'的东西(少模块)。 – Trees4theForest

+0

基本承诺做了很多,但我更喜欢便利的方法。 '.spread','promisifyAll'和'Promise.map'。尽管节点现在支持基本的承诺,但蓝鸟仍然常用。尽管你可以在没有蓝鸟的情况下完成大部分上述任务。 '.spread'可以只是'.then',你可以手动从结果数组中读取数据。宣布'fs'功能虽然会是一些样板。 Bluebird的'Promise.map'必须用一个手工array.map替换为一个promise数组。 – Chev

+0

也感谢您的异步/等待提示。不知道我已经可以检查出来了! :D – Chev