5

为了掌握Q.js,我想使用async.seriesin转换以下代码Q.js。基本上,如果文件夹不存在(使用 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。

4

2 回答 2

10

关键是在开始之前将 node.js 函数转换为返回 Promise Q.denodeify,这意味着文件的标题应该如下所示:

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

我会说,一旦你编写了帮助程序,代码对于 Promise 来说比处理回调所需的所有错误处理内容要清晰得多。我想说的是,当您手动编写或不跟踪所有这些中间结果时,它会更清楚。

加起来

您可能会也可能不会认为这些示例比async.series版本更清晰。考虑一下您对该功能的了解程度。它实际上是以一种非常不透明的方式做一些非常复杂的事情。我最初假设只会返回最后一个结果(ala 瀑布),并且必须在 Async 的文档中查找它。我几乎不需要在 Promise 库的文档中查找某些内容。

于 2013-08-29T15:39:15.000 回答
2

让您的每个函数都返回一个承诺。用Deferred构造它们:

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;
}

但是,有一些用于节点样式回调的辅助函数,因此您无需err每次都自行检查。随之Q.nfcall变成

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

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

如果您已为所有功能执行此操作,则可以将它们链接then

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

请注意,它的工作方式类似于 async 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);
});
于 2013-08-29T12:13:54.993 回答