使用节点异步函数避免Promise反模式

时间:2015-08-16 19:18:14

标签: javascript node.js es6-promise

我已经意识到Promise的反模式,并担心我可能会在这里堕落(请参阅函数中的代码提取)。可以看出,我将Promises嵌套在两个节点异步函数中。我能够暴露内部Promise的唯一方法是使用外部Promise。我欢迎指导如何更优雅地写这个。

function xyz() {
    return new Promise(function(resolve, reject) {
        return Resto.find({recommendation: {$gte: 0}}, function (err, data) {
            if (err) return reject("makeSitemap: Error reading database");

            return fs.readFile(__dirname + '/../../views/sitemap.jade', function(err, file) {
                if (err) return reject("makeSitemap: Error reading sitemap template");

                [snip]

                resolve(Promise.all([
                    NLPromise('../m/sitemap.xml', map1),
                    NLPromise('../m/sitemap2.xml', map2)
                ]));
            });
        });
    });
}

我也在plnkr

中发现了这个问题

3 个答案:

答案 0 :(得分:1)

最好的解决方案是创建回调使用函数的promise版本。 bluebird,一个出色的承诺实现(优于Node的本机实现),内置了这个。

Bluebird.promisifyAll(Resto); // If you can promisify the prototype, that’s better
Bluebird.promisifyAll(fs);

function xyz() {
    return Resto.findAsync({recommendation: {$gte: 0}}).then(function (data) {
        return fs.readFileAsync(__dirname + '/../../views/sitemap.jade').then(function (file) {
            …

            return Bluebird.all([
                NLPromise('../m/sitemap.xml', map1),
                NLPromise('../m/sitemap2.xml', map2)
            ]);
        });
    });
}

此外,如果fs.readFile不依赖Resto.findAsync,您应该将它们一起运行:

return Bluebird.all([
    Resto.findAsync({recommendation: {$gte: 0}}),
    fs.readFileAsync(__dirname + '/../../views/sitemap.jade'),
]).spread(function (data, file) {
    …

    return Bluebird.all([
        NLPromise('../m/sitemap.xml', map1),
        NLPromise('../m/sitemap2.xml', map2),
    ]);
});

答案 1 :(得分:0)

根据minitech的建议,我写道:

readFileAsync = function(fname) {
    return new Promise(function(resolve, reject) {
        fs.readFile(fname, function (err, data) {
            if (err) return reject(err);
            resolve(data);
        })
    });
};

然后使用robertklep观察到Mongoose可以回复承诺,我最终得到了:

var datasets;   // seemingly unavoidable semi-global variable with simple Promises
return Resto.find({recommendation: {$gte: 0}})
    .then(function(data) {
        datasets = _.partition(data, function(r) {
            return _.includes([0,1], r.recommendation);
        });

        return readFileAsync(__dirname + '/../../views/sitemap.jade');
    })
    .then(function(file) {
        [snip]

        return Promise.all([
            NLPromise('../m/sitemap.xml', map1),
            NLPromise('../m/sitemap2.xml', map2)
        ]);
    });

我可以继续使用How do I access previous promise results in a .then() chain?

处理全局变量

答案 2 :(得分:0)

由于标记了es6-promise,我想留下一个不使用扩展程序的es6答案:

var xyz = () => new Promise((r, e) => Resto.find({recommendation: {$gte: 0}},
                                                 err => err ? e(err) : r()))
  .then(() => new Promise((r, e) =>
      fs.readFile(__dirname + '/../../views/sitemap.jade',
                  err => err? e(err) : r())))
  // [snip]
  .then(() => Promise.all([
    NLPromise('../m/sitemap.xml', map1),
    NLPromise('../m/sitemap2.xml', map2)
  ]));

要避免的反模式:

  • 嵌套。尽可能展平链条。狭隘地使用new Promise
  • 隐藏错误。尽可能找出失败的真正原因。
  • 投掷弦乐。抛出真正的错误对象,因为它们有堆栈跟踪。