在递归中使用javascript promises进行Api调用

时间:2015-09-27 14:16:44

标签: javascript ecmascript-6 es6-promise

我想使用gitter api从房间获取所有消息。

我需要做的是发送get api请求,例如50项,onComplete我需要发送另外50个项目的请求,并跳过我已经收到的50个项目。请求此请求,直到他们无法退回任何项目。所以:

  • 发送api请求
  • json解析它
  • :请求有项目
    • 使用此项目进行SQL查询
    • 继续查询
    • 发送下一个api请求(递归?)
    • ?如果下一个api请求中没有更多项目 - 显示完成消息
  • :请求没有商品
    • 中止消息

我正在尝试承诺,但我对他们有点困惑,不知道,如果我做的一切都是正确的。主要问题是如果所有呼叫都完成,则下一个Api呼叫和回叫。这是我的代码:

class Bot {
  //...

  _mysqlAddAllMessages(limit, skip) {
    promise('https://api.gitter.im/v1/rooms/' + this.room + '/chatMessages' +
        '?access_token=' + config.token + '&limit=' + limit + '&skip=' + skip)
        .then(function (response) {
          return new Promise(function (resolve, reject) {
            response = JSON.parse(response);

            if (response.length) {
              console.log(`Starting - limit:${limit}, skip:${skip}`);

              resolve(response);
            }
          })
        }).then(response => {
          let messages = response,
              query = 'INSERT INTO messages_new (user_id, username, message, sent_at) VALUES ';

          for (let message of messages) {
            let userId = message.fromUser.id,
                username = message.fromUser.username,
                text = message.text.replace(/["\\]/g, '|'),
                date = message.sent;

            query += '("' + userId + '", "' + username + '", "' + text + '", "' + date + '"), ';
          }

          query = query.substr(0, query.length - 2);

          return new Promise((resolve, reject) => {
            this.mysql.getConnection((error, connection) => {
              connection.query(query, (err) => {
                if (err) {
                  reject(`Mysql Error: ${err}`);
                } else {
                  connection.release();

                  resolve(console.log(`Added ${messages.length} items.`));
                }
              });
            });
          });
        })
        .then(()=> {
          // what to do here
          return this._mysqlAddAllMessagesf(limit, skip += limit)
        })
        .catch(function (er) {
          console.log(er);
        })
        .finally(function () {
          console.log('Message fetching completed.');
        });
  }
}

let bot = new Bot();
bot._mysqlAddAllMessages(100, 0);

也许你可以检查并帮助我?或者为这些事情提供类似的代码?

更新

以下是我重构代码的内容:jsfiddle

1 个答案:

答案 0 :(得分:3)

你的代码让我非常困惑。将promises与异步操作一起使用的最简单方法是“保证”现有的异步操作,然后使用promises编写所有逻辑。 “promisify”意味着生成或编写一个返回promise而不是仅使用回调的包装函数。

首先,让我们看一下整体逻辑。根据你的问题,你说你有一个API,你想要一次性获取50个项目,直到你得到它们为止。这可以通过类似递归的结构来完成。创建一个内部函数,执行检索并返回一个promise,每次完成时再次调用它。假设您有两个核心功能,一个名为getItems(),它从您的API获取项目并返回一个承诺,一个名为storeItems()的项目将这些项目存储在您的数据库中。

function getAllItems(room, chunkSize, token) {
    var cntr = 0;
    function getMore() {
        return getItems(room, cntr, chunkSize, token).then(function(results) {
            cntr += results.length;
            if (results.length === chunkSize) {
                return storeItems(results).then(getMore);
            } else {
                return storeItems(results);
            }
        });
    }
    return getMore();        
}

此代码使用链接承诺,这是承诺的稍微高级但非常有用的功能。当您从.then()处理程序返回一个promise时,它会链接到前一个promise,自动将它们全部链接到一系列操作中。然后,最终的返回结果或错误将通过原始承诺返回给原始调用者。同样,此链中可能发生的任何错误都会一直传播回原始调用方。这在具有多个异步操作的复杂函数中非常有用,如果使用常规回调,则不能简单地返回或抛出。

然后会这样调用:

getAllItems(this.room, 50, config.token).then(function() {
    // finished successfully here
}, function(err) {
    // had an error here
});

现在,我将针对您的低级调用的已创建的promisified版本的一些示例进行操作,以实现getItems()storeItems()。回到那一刻。

我并不完全了解您的异步操作中的所有细节,因此这不是一个完整的示例,但应说明整体概念,然后您可以询问有关实施的任何必要的澄清问题。

在宣传函数时,您要做的主要事情是将处理回调和错误条件的脏工作封装到一个返回promise的核心函数中。然后,您可以在基于承诺的良好代码中使用此函数,并允许您在控制流中使用promises的非常好的错误处理功能。

对于请求项目,看起来您构造了一个URL,该URL在URL中获取了大量参数,并以JSON形式返回结果。我假设这是一个node.js环境。那么,以下是使用node.js getItems()模块进行request()实现的方法。这将返回一个promise,其解析值将是已经解析的表示api调用结果的Javascript对象。

function getItems(room, start, qty, token) {
    return new Promise(function(resolve, reject) {
        var url = 'https://api.gitter.im/v1/rooms/' + room + '/chatMessages' + '?access_token=' + token + '&limit=' + qty + '&skip=' + start;
        request({url: url, json: true}, function(err, msg, result) {
            if (err) return reject(err);
            resolve(result);
        });
    });
}    

为了存储物品,我们希望完成同样的事情。我们想创建一个函数,它将数据作为参数存储并返回一个promise,它完成函数内部的所有脏工作。从逻辑上讲,你想拥有这样的结构:

function storeItems(data) {
    return new Promise(function(resolve, reject) {
        // do the actual database operations here
        // call resolve() or reject(err) when done
    });
}

很抱歉,但我不太明白你在mySql数据库中做了什么,足以完全填写这个功能。希望这个结构能让你充分了解如何完成它或者如果你遇到问题就提出一些问题。

注意:如果您使用像Bluebird这样的Promise库来添加其他承诺功能,那么宣传现有操作是Bluebird中内置的,因此getItems()就是这样:

var Promise = require('bluebird');
var request = Promise.promisifyAll(require('request'));

function getItems(room, start, qty, token) {
    var url = 'https://api.gitter.im/v1/rooms/' + room + '/chatMessages' + '?access_token=' + token + '&limit=' + qty + '&skip=' + start;
    return request.getAsync({url: url, json: true});
}    
相关问题