使用 Q 承诺 - 有没有办法减少这段代码的重复性?

Posted

技术标签:

【中文标题】使用 Q 承诺 - 有没有办法减少这段代码的重复性?【英文标题】:Using Q promises - is there a way to make this code less repetitive? 【发布时间】:2014-02-25 17:02:37 【问题描述】:

我正在为我的 Node.js 应用程序启动一个集成测试套件。我目前正在尝试编写一个设置脚本来擦除测试数据库并用一些测试数据填充它。我试图避免可怕的"Pyramid of Doom",我希望使用 Promise 来防止我的代码失控。我对 Promise 很陌生,但我仍在努力思考它们 - 可能我没有正确使用它们。

这是我没有承诺的初始版本。这可行,但在 wazoo 上嵌套了回调:

var mongoose = require('mongoose');
var User = require('./user');
var MONGODB_URL = process.env.MONGODB_TEST_URL || 'localhost:27017/swot_test';

console.log('\nRunning e2e test preparation script');
console.log('-----------------------------------\n');

console.log('Connecting to database:', MONGODB_URL, '...')
mongoose.connect(MONGODB_URL, function () 

    console.log('Wiping database...')
    mongoose.connection.db.dropDatabase(function () 

        console.log('Setting up test user...')
        User.createUser(
            email: 'test@example.com',
            password: 'tester'
        , function (err, user) 
            if (err) throw err;

            // If there was more setup, it would have to go here... pyramid of doom!

            console.log('Finished.');
            process.exit();
        );
    );
);

这是一个使用Q promises的版本:

var Q = require('q');
var mongoose = require('mongoose');
var User = require('./user');
var MONGODB_URL = process.env.MONGODB_TEST_URL || 'localhost:27017/swot_test';

console.log('\nRunning e2e test preparation script');
console.log('-----------------------------------\n');

Q.fcall(function () 
    var deferred = Q.defer();
    console.log('Connecting to database:', MONGODB_URL, '...');
    mongoose.connect(MONGODB_URL, function (err) 
        if (err) deferred.reject(new Error(err));
        else deferred.resolve();
    );
    return deferred.promise;
)
.then(function () 
    var deferred = Q.defer();
    console.log('Wiping database...');
    mongoose.connection.db.dropDatabase(function (err) 
        if (err) deferred.reject(new Error(err));
        else deferred.resolve();
    );
    return deferred.promise;
)
.then(function () 
    var deferred = Q.defer();
    console.log('Setting up test user...');
    User.createUser(
        email: 'test@example.com',
        password: 'tester'
    , function (err, user) 
        if (err) deferred.reject(new Error(err));
        else deferred.resolve();
    );
    return deferred.promise;
)
.done(function () 
    console.log('Finished.');
    process.exit();
, function (err) 
    console.error('An error occurred:', err.stack);
);

我喜欢它有更少的嵌套,但里面有很多重复。有没有办法可以使用Q API 中的辅助函数来使这段代码更简洁、更少重复?尤其是这部分:

if (err) deferred.reject(new Error(err));
else deferred.resolve();

对于清理此代码的任何帮助,我将不胜感激。

【问题讨论】:

【参考方案1】:
Q.ninvoke(mongoose,'connect', MONGODB_URL)
 .then(function () 
    console.log('Wiping database...');
    return Q.ninvoke(mongoose.connection.db, 'dropDatabase');
 )
 .then(function () 
    console.log('Setting up test user...')
    return Q.ninvoke(User, 'createUser', 
        email: 'test@example.com',
        password: 'tester'
    );
 )
 .then(function (user) 
    console.log('Finished.');
    process.exit();
 )
 .catch(function(err) 
    console.log(err);
 );

【讨论】:

【参考方案2】:

您可以使用可重复使用的 oncomplete 助手来缩短它,如下所示:

function oncomplete(deferred) 
    return function (err, result) 
        if (err) deferred.reject(new Error(err));
        else deferred.resolve(result);
    


Q.fcall(function () 
    var deferred = Q.defer();
    console.log('Connecting to database:', MONGODB_URL, '...');
    mongoose.connect(MONGODB_URL, oncomplete(deferred));
    return deferred.promise;
)
.then(function () 
    var deferred = Q.defer();
    console.log('Wiping database...');
    mongoose.connection.db.dropDatabase(oncomplete(deferred));
    return deferred.promise;
)
.then(function () 
    var deferred = Q.defer();
    console.log('Setting up test user...');
    User.createUser(
        email: 'test@example.com',
        password: 'tester'
    , oncomplete(deferred));
    return deferred.promise;
)
.done(function () 
    console.log('Finished.');
    process.exit();
, function (err) 
    console.error('An error occurred:', err.stack);
);

如果你足够勇敢,你可以用Node v0.11 beta and yield 关键字大大简化它。这将实现一个异步状态机,因此您可以在没有显式回调的情况下拥有伪线性代码流。

【讨论】:

@down-voter: 请发表评论,这样我们都可以了解我的答案有哪些错误。【参考方案3】:

查看async 模块,特别是waterfall。你可以在这里阅读:

https://github.com/caolan/async#waterfalltasks-callback

基本上,它允许您以简洁明了的方式链接一组嵌套回调。

【讨论】:

这被否决的任何原因?这是清理上面代码的完美方式。 我认为没有原因。我对你的回答做了一个小的编辑,如果他/她做错了,这让投反对票的人有机会撤消它。 异步并不是真正基于承诺的。我认为这使得这种投票变得不受欢迎。问题是关于“使用 Q 承诺”。

以上是关于使用 Q 承诺 - 有没有办法减少这段代码的重复性?的主要内容,如果未能解决你的问题,请参考以下文章

nodejs 中的异步和 Q 承诺

猫鼬承诺和Q承诺

猫鼬和 q 承诺

承诺问题:使用 Q.nfcall() 调用 mongoose.findOne()

我应该如何声明返回没有数据的 $q 承诺的函数的返回类型?

如何使用 .success 和 .error 在 Angularjs 中扩展 $q 承诺