重构同步代码以释放node.js异步性的威力
我们长期使用Python和PHP的程序员都有一些同步代码(下面的示例)。大多数函数都具有异步对应项。我们真的很想“获得”Javascript和Node的强大功能,并且相信这是一个理想的例子,异步Node.js可以加快速度,让我们大吃一惊 利用异步节点重构以下内容的教科书方法是什么<代码>异步/重构同步代码以释放node.js异步性的威力,node.js,asynchronous,Node.js,Asynchronous,我们长期使用Python和PHP的程序员都有一些同步代码(下面的示例)。大多数函数都具有异步对应项。我们真的很想“获得”Javascript和Node的强大功能,并且相信这是一个理想的例子,异步Node.js可以加快速度,让我们大吃一惊 利用异步节点重构以下内容的教科书方法是什么异步/等待和承诺。全部?怎样(使用节点8.4.0。向后兼容性不受关注。) 注: 我们需要保持功能模块化和可重用 为filesObj获取内容的函数比这里列出的要多 大多数函数都可以重新编写为异步的,有些则不能 理想情况下
等待
和承诺。全部
?怎样(使用节点8.4.0。向后兼容性不受关注。)
注:
- 我们需要保持功能模块化和可重用
- 为
获取内容的函数比这里列出的要多filesObj
- 大多数函数都可以重新编写为异步的,有些则不能
- 理想情况下,我们需要保持
文件列表的原始顺序
- 理想情况下,我们希望使用最新的节点和JS特性,而不是依赖外部模块
异步获取md5的各种文件流方法:
getCDate
、getFSize
和getMd5
都是异步的,并被承诺,然后将它们封装在另一个异步承诺返回函数中,这里称为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就很简单了。所有:
Promise.all(promises)
.then(filesObj => /*do something*/)
.catch(err => /*handle error*/)
这使得事情模块化,不需要异步/等待,允许您将额外的函数插入到
statFile
,并保留您的文件顺序。有多种不同的方法可以异步处理此代码。您可以使用节点库更优雅地处理所有回调。如果你不想陷入承诺,那么这是一个“简单”的选择。我在引号中加上了easy,因为如果你理解得足够好,承诺实际上会更容易。异步库很有帮助,但在错误传播方面仍有许多需要改进的地方,并且有许多样板代码需要您将所有调用封装在其中
更好的方法是使用承诺。Async/Await仍然是一个新概念。如果没有像Bable或Typescript这样的预处理器,甚至在节点7(不确定节点8)中都不受支持。另外,async/await无论如何都在幕后使用承诺
下面是我将如何使用Promissions实现这一点,甚至包括一个文件统计缓存以实现最高性能:
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版本的节点中之后,我决定在昨晚玩一玩它。对于不需要并行完成的递归异步任务,或者对于您可能希望能够同步编写的嵌套异步任务,它似乎最有用。对于这里需要的内容,我看不到使用async/await的任何令人兴奋的方法,但是在一些地方,代码可以读得更清楚。下面是代码,但有一些异步/等待的便利
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);
令人惊叹的谢谢你的建议
async
/await
是自7.6以来node的一部分,我相信promise也是标准的。不要认为我们需要bluebird(不确定是否需要promisfy),我们更愿意尽可能地做一些“本地”的事情(减少模块)。基本承诺可以做很多事情,但我更喜欢方便的方法.spread
、promisifyAll
和Promise.map
。尽管现在节点中支持基本承诺,但Bluebird仍然被广泛使用。你当然可以在没有蓝鸟的情况下完成以上大部分工作.spread
可以是一个。然后
可以手动读取结果数组。不过,演示fs
函数有点像样板。Bluebird的Promise.map
必须用手动数组来代替。映射到promises数组。还要感谢有关async/await的提示。不知道我可能已经在检查他们了!:DI昨晚对async/await进行了一些探索,并在代码中找到了一些有用的地方。主要是在助手函数中,用于获取文件统计信息或文件内容。这些函数现在看起来更具可读性和同步性。我尝试在Promise.all逻辑中添加async/await sugar,但实际上我觉得它使代码的这一部分变得过于臃肿,因为它并行处理递归异步代码。我在答案中添加了另一个编辑,显示了我的更改:)要从arrow函数返回一个对象,您需要将它括在括号中,这样它就不会将括号误认为代码块的开头<代码>(datetime,filesize,md5hash)=>({datetime,filesize,md5hash})代码>:)@Chev谢谢,我不知道这个问题的答案。没有解释的否决票是没有用的。请帮助改进这个问题。我希望否决票弹出一个框,要求用户至少输入一个简短的原因。
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);