重构同步代码以释放node.js异步性的威力

重构同步代码以释放node.js异步性的威力,node.js,asynchronous,Node.js,Asynchronous,我们长期使用Python和PHP的程序员都有一些同步代码(下面的示例)。大多数函数都具有异步对应项。我们真的很想“获得”Javascript和Node的强大功能,并且相信这是一个理想的例子,异步Node.js可以加快速度,让我们大吃一惊 利用异步节点重构以下内容的教科书方法是什么异步/等待和承诺。全部?怎样(使用节点8.4.0。向后兼容性不受关注。) 注: 我们需要保持功能模块化和可重用 为filesObj获取内容的函数比这里列出的要多 大多数函数都可以重新编写为异步的,有些则不能 理想情况下

我们长期使用Python和PHP的程序员都有一些同步代码(下面的示例)。大多数函数都具有异步对应项。我们真的很想“获得”Javascript和Node的强大功能,并且相信这是一个理想的例子,异步Node.js可以加快速度,让我们大吃一惊

利用异步节点重构以下内容的教科书方法是什么<代码>异步/
等待
承诺。全部
?怎样(使用节点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);