在递归中使用 javascript 承诺进行 Api 调用

Make an Api call with javascript promises in recursion

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

我需要做的是发送 get api 请求,例如50 个项目,onComplete 我需要发送另一个包含 50 个项目的请求并跳过我已经收到的 50 个项目。执行此请求,直到他们不会 return 任何项目。所以:

我正在为此尝试 Promises,但我对它们有点困惑,不知道我是否做对了一切。主要问题是下一个 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

你的代码让我很困惑。将 promises 与异步操作一起使用的最简单方法是 "promisify" 您现有的异步操作,然后使用 promises 编写所有逻辑。 "promisify" 某物意味着生成或编写一个包装函数,该函数 return 是一个承诺,而不是仅使用回调。

首先,让我们看一下整体逻辑。根据你的问题,你说你有一个 API ,你想调用它一次获取 50 个项目,直到你得到所有项目。这可以通过类似递归的结构来完成。创建一个内部函数来执行检索和 returns 承诺,每次完成时再次调用它。假设这里涉及两个核心函数,一个叫做 getItems(),它从你的 API 和 return 中获取项目,一个叫做 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();        
}

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

然后会这样调用:

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

现在,我将处理一些示例,以创建低级别调用的承诺版本以实现 getItems()storeItems()。稍后再回来。

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

当承诺一个函数时,您要做的主要事情是将处理回调和错误条件的脏工作封装到一个 return 承诺的核心函数中。然后允许您在干净流畅的基于 promise 的代码中使用此函数,并允许您在控制流中使用 promise 的真正强大的错误处理功能。

为了请求项目,您似乎构建了一个 URL,它在 URL 中接受了一堆参数,然后您在 JSON 中得到了结果。我假设这是一个 node.js 环境。因此,下面是如何使用 node.js request() 模块执行 getItems() 实现。此 return 是一个承诺,其解析值将是代表 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);
        });
    });
}    

对于存储项目,我们想要完成同样的事情。我们想创建一个函数,将数据作为参数存储,return 是一个承诺,它会在函数内部完成所有脏工作。所以从逻辑上讲,你想要这样的结构:

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 库来添加额外的 promise 功能,那么 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});
}