Q.js:如何重写Q.js中的asynchronous系列stream?

为了掌握Q.js ,我想在Q.js使用async.series来转换下面的代码。 基本上我创build一个文件夹,如果它不存在(使用mkdirp),将文件移动到备份文件夹,并将文件保存到主文件夹。

 var async = require('async'); var fs = require('fs'); var path = require('path'); var sessiondId = new Date().getTime() % 2 == 0 ? new Date().getTime().toString() : '_1234'; var backupFolder = path.join(__dirname,sessiondId); var backupFullPath = path.join(backupFolder,'a.txt'); var fullPath = path.join(__dirname,'main','a.txt'); var mkdirp = require('mkdirp'); async.series({ createOrSkip: function(callback) { mkdirp(backupFolder, function (err, dir) { if(err) { callback(err, null); } else { callback(null, {created: !!dir, folderAt: backupFolder}); } }); }, move: function(callback) { fs.rename(fullPath, backupFullPath, function(err) { if(err) { callback(err, null); } else { callback(null, {backupAt: backupFullPath}); } }); }, write: function(callback) { fs.writeFile(fullPath, 'abc', function(err) { if (err) { callback(err, null); } else { callback(null, {saveAt: fullPath}); } }); } }, function(err, result) { console.log(result); }); 

其实我不知道从哪里开始。 谢谢你的帮助。

R.

关键是在开始之前将node.js函数转换为使用Q.denodeify来返回promise,这意味着文件头应该如下所示:

 var Q = require('q') var fs = require('fs'); var path = require('path'); var sessiondId = new Date().getTime() % 2 == 0 ? new Date().getTime().toString() : '_1234'; var backupFolder = path.join(__dirname,sessiondId); var backupFullPath = path.join(backupFolder,'a.txt'); var fullPath = path.join(__dirname,'main','a.txt'); var mkdirp = Q.denodeify(require('mkdirp')); var rename = Q.denodeify(fs.rename); var writeFile = Q.denodeify(fs.writeFile); 

如果node.js原生支持promise,则不需要这种更改。

选项1

 // createOrSkip mkdirp(backupFolder) .then(function (dir) { // move return rename(fullPath, backupFullPath); }) .then(function () { // write return writeFile(fullPath, 'abc'); }) .done(function () { console.log('operation complete') }); 

我认为这并不是那么简单。 就像@Bergi说的那样,它更像“瀑布”。 如果你想要系列的确切行为(但是有承诺),你将不得不使用类似选项2或选项3的东西。

选项2

你可以手动写出代码来保存结果。 我通常会发现,虽然这需要一点额外的写作,但它是迄今为止最容易阅读的:

 var result = {} mkdirp(backupFolder) .then(function (dir) { result.createOrSkip = {created: !!dir, folderAt: backupFolder}; return rename(fullPath, backupFullPath); }) .then(function () { result.move = {backupAt: backupFullPath}; return writeFile(fullPath, 'abc'); }) .then(function () { result.write = {saveAt: fullPath}; return result; }) .done(function (result) { console.log(result); }); 

选项3

如果你总是发现自己使用这种代码,你可以写一个非常简单的系列助手(我从来没有发现需要亲自去做):

 function promiseSeries(series) { var ready = Q(null); var result = {}; Object.keys(series) .forEach(function (key) { ready = ready.then(function () { return series[key](); }).then(function (res) { result[key] = res; }); }); return ready.then(function () { return result; }); } promiseSeries({ createOrSkip: function () { return mkdirp(backupFolder).then(function (dir) { return {created: !!dir, folderAt: backupFolder}; }); }, move: function () { return rename(fullPath, backupFullPath) .thenResolve({backupAt: backupFullPath}); }, write: function () { return writeFile(fullPath, 'abc') .thenResolve({saveAt: fullPath}); } }).done(function (result) { console.log(result); }); 

我会说,一旦你写了帮手,代码就比使用callback工作所需的所有error handling代码更加清晰。 我想说的是,当你用手写或不logging所有这些中间结果时,它会更清晰。

加起来

您可能会也可能不会认为这些示例比async.series版本更清晰。 考虑一下你可能知道这个function。 其实是以非常不透明的方式做一件相当复杂的事情。 我最初认为只有最后的结果将被返回(ala瀑布),并且必须在Async的文档中查找它。 我几乎从不需要在Promise库的文档中查找某些东西。

让你的每个function都回复一个承诺。 用延期构build它们:

 function createOrSkip(folder) { var deferred = Q.defer(); mkdirp(folder, function (err, dir) { if(err) { deferred.reject(err); } else { deferred.resolve({created: !!dir, folderAt: backupFolder}); } }); return deferred.promise; } 

不过, 对于节点式的callback函数 ,还有一些辅助函数,所以你不需要每次都检查一下err 。 随着Q.nfcall它成为

 function createOrSkip(folder) { return Q.nfcall(mkdirp, folder).then(function transform(dir) { return {created: !!dir, folderAt: backupFolder}; }); } 

transform函数会将结果( dir )映射到您期望的对象。

如果你已经完成了所有的function,你可以用下面的方法链接它们 :

 createOrSkip(backupfolder).then(function(createResult) { return move(fullPath, backupFullPath); }).then(function(moveResult) { return write(fullPath, 'abc'); }).then(function(writeResult) { console.log("I'm done"); }, function(err) { console.error("Something has failed:", err); }); 

请注意,这是像asynchronous的waterfall ,而不是series ,即中间结果将会丢失。 为了达到这个目的,你需要嵌套它们:

 createOrSkip(backupfolder).then(function(createResult) { return move(fullPath, backupFullPath).then(function(moveResult) { return write(fullPath, 'abc');.then(function(writeResult) { return { createOrSkip: createResult, move: moveResult, write: writeResult }; }); }); }).then(function(res){ console.log(res); }, function(err) { console.error("Something has failed:", err); });