【问题标题】:How I should use promises for db + http calls in node js我应该如何在节点 js 中使用对 db + http 调用的承诺
【发布时间】:2017-09-15 17:48:25
【问题描述】:

我需要实现的系统

  • 从父集合中获取数据。
  • 检查是否在 redis 中找到特定键
  • 如果没有,则执行 http 调用并获取 json 数据,然后设置缓存
  • 如果是,则从缓存中获取数据
  • 将数据保存到父 ID 的子集合中。

我有使用类似回调的工作解决方案。

MongoClient.connect(dsn).then(function(db) {
    parentcollection.findOne({"_id" : new ObjectId(pid)}, function(err, data) {

    var redis = require("redis"),
    client = redis.createClient();

    client.on("error", function (err) {
        console.log("Error " + err);
    });

    // If not set

    client.get(cacheKey, function(err, data) {
        // data is null if the key doesn't exist
        if(err || data === null) {
            var options = {
                host: HOST,
                port: 80,
                path: URI
            };

            var req = http.get(options, function(res) {
                res.setEncoding('utf8');
                res.on('data', function (chunk) {
                    body += chunk;
                    //console.log('CHUNK: ' + chunk);
                });
                res.on('end', function () {
                    data = JSON.parse(body);


                    // Get childdata After process of data

                    childcollection.save(childdata, {w:1}, function(cerr, inserted) {
                        db.close();

                    });
                });
            }); 
        } else {
            // Get childdata from cache data
            childcollection.save(childdata, {w:1}, function(cerr, inserted) {
                db.close();
            });
        }
    });
});

我想使用 promise(原生的,而不是像 bluebird / request 这样的外部的)而不是回调。我检查手册并考虑是否需要这样实施

var promise1 = new Promise((resolve, reject) => {

    MongoClient.connect(dsn).then(function(db) {
        parentcollection.findOne({"_id" : new ObjectId(pid)}, function(err, data) {


    });

}}.then(function(data){
   var promise2 = new Promise((resolve, reject) => {
     var redis = require("redis"),
        client = redis.createClient();

        client.on("error", function (err) {
            console.log("Error " + err);
        });

        // If not set

        client.get(cacheKey, function(err, data) {
            // data is null if the key doesn't exist
            if(err || data === null) {
                var options = {
                    host: HOST,
                    port: 80,
                    path: URI
                };

                var promise3 = new Promise((resolve, reject) => {
                        var req = http.get(options, function(res) {
                            res.setEncoding('utf8');
                            res.on('data', function (chunk) {
                                body += chunk;
                                //console.log('CHUNK: ' + chunk);
                            });
                            res.on('end', function () {
                                data = JSON.parse(body);


                            // Get childdata After process of data


                        });
                    })
                }).then(function(data){
                    childcollection.save(childdata, {w:1}, function(cerr, inserted) {
                                db.close();

                            });
                });
            } else {
                // Get childdata from cache data
                childcollection.save(childdata, {w:1}, function(cerr, inserted) {
                    db.close();
                });
            }
        });

}}.then(function(data){
});
});              

哪个看起来像回调地狱一样肮脏,或者没有使用上述承诺的任何更好的方法?

【问题讨论】:

  • new Promise 调用分解为额外的承诺返回函数。

标签: node.js callback promise node-mongodb-native node-redis


【解决方案1】:

一个问题是您永远不会调用提供给 promise 构造函数回调的 resolve 函数。如果不调用它们,promise 永远不会解决。

我建议在单独的、可重用的函数中创建这些新的 Promise。另一方面,当你不提供回调参数时,一些 MongoDb 方法已经返回了 Promise。

你可以像下面那样做。

// Two promisifying functions:
function promiseClientData(client, key) {
    return new Promise(function (resolve, reject) {
        return client.get(key, function (err, data) {
            return err ? reject(err) : resolve(data); // fulfull the promise
        });
    });
}

function promiseHttpData(options) {
    return new Promise(function (resolve, reject) {
        return http.get(options, function(res) {
            var body = ''; // You need to initialise this...
            res.setEncoding('utf8');
            res.on('data', function (chunk) {
                body += chunk;
                //console.log('CHUNK: ' + chunk);
            });
            res.on('end', function () {
                data = JSON.parse(body);
                resolve(data);  // fulfull the promise
            });
        );
    });
}

// Declare the db variable outside of the promise chain to avoid 
// having to pass it through
var db;

// The actual promise chain:
MongoClient.connect(dsn).then(function (dbArg) {
    db = dbArg;
    return parentcollection.findOne({"_id" : new ObjectId(pid)}); // returns a promise
}).then(function (data) {
    var redis = require("redis"),
    client = redis.createClient();
    client.on("error", function (err) {
        console.log("Error " + err);
    });
    // Get somehow cacheKey...
    // ...
    return promiseClientData(client, cacheKey);
}).then(function (data) {
    // If not set: data is null if the key doesn't exist
    // Throwing an error will trigger the next `catch` callback
    if(data === null) throw "key does not exist"; 
    return data;
}).catch(function (err) {
    var options = {
        host: HOST,
        port: 80,
        path: URI
    };
    return promiseHttpData(options);
}).then(function (data) {
    // Get childdata by processing data (in either case)
    // ....
    // ....
    return childcollection.save(childdata, {w:1}); // returns a promise
}).then(function () {
    db.close();
});

我假设 MongoDb 返回的承诺是完全合规的。如有疑问,您可以通过在它们上调用 Promise.resolve() 将它们转换为原生 JavaScript 承诺,例如:

return Promise.resolve(parentcollection.findOne({"_id" : new ObjectId(pid)}));

或:

return Promise.resolve(childcollection.save(childdata, {w:1}));

【讨论】:

  • 感谢您的示例代码帮助我通过 Promise 编写代码,但是我不使用 throw/catch 作为 redis 的条件流,而是使用类似于stackoverflow.com/a/33260670/1230744的功能流
  • 当然可以。确保避免代码重复:childcollection.save 最好只出现一次。另外,如果您不使用catch,请注意,如果发生异常,则链会中断并且无法再捕获错误。 Throw/catch 在 Promise 链中非常有效。
  • 是的,我正在使用 catch 处理承诺链中的所有一般故障,这是我犹豫使用 catch 处理 redis 密钥未命中的主要原因,这是正常事件。
  • 我明白了,它在这里看起来很方便,因为您想在出现错误和没有键匹配的情况下做同样的事情(参见原始代码中的if(err || data === null))。
  • 好的,我只是从 redis 示例示例中复制和粘贴代码,我没有意识到同一行同时处理致命错误和空缓存情况。
猜你喜欢
  • 2018-09-26
  • 1970-01-01
  • 2016-05-17
  • 1970-01-01
  • 1970-01-01
  • 2016-09-08
  • 2015-10-28
  • 2016-02-02
  • 1970-01-01
相关资源
最近更新 更多