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

Make an Api call with javascript promises in recursion

本文关键字:Api 调用 承诺 javascript 递归      更新时间:2023-09-26

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

我需要做的是发送带有50个项目的get api请求,onComplete我需要发送另一个带有50个项目的请求,并跳过我已经收到的50个项目。这样做,直到他们不会退回任何东西。所以:

  • 发送api请求
  • 解析解析
  • :请求有项
    • 创建一个sql查询
    • 继续查询
    • 发送下一个api请求(递归?)
    • ?如果下一个api请求中没有更多的项目-显示完成消息
  • :请求没有项目
    • abort with message

我正在尝试承诺,但我对他们有点困惑,不知道,如果我做的一切都是正确的。如果所有调用都完成了,主要问题是下一个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

你的代码让我很困惑。在异步操作中使用promise的最简单方法是"承诺"现有的异步操作,然后使用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
});

现在,我将研究一些示例,以创建较低级调用的承诺版本来实现getItems()storeItems()。回头再看那些

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

当承诺一个函数时,你要做的主要事情是将处理回调和错误条件的脏工作封装到一个返回承诺的核心函数中。这样,你就可以在干净流畅的基于承诺的代码中使用这个函数,并允许你在控制流中使用承诺的真正强大的错误处理能力。

对于请求项目,看起来您构建了一个URL,该URL接受URL中的一堆参数,然后以JSON格式返回结果。我假设这是一个node.js环境。所以,这里是如何使用node.js request()模块来实现getItems()。这将返回一个promise,它的解析值将是已经解析过的Javascript对象,表示api调用的结果。

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这样的承诺库来添加额外的承诺功能,那么承诺一个现有的操作是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});
}