【问题标题】:Make an Api call with javascript promises in recursion在递归中使用 javascript 承诺进行 Api 调用
【发布时间】:2015-09-27 14:16:44
【问题描述】:

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

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

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

我正在为此尝试 Promise,但我对它们有点困惑,不知道我是否做对了所有事情。如果所有调用都已完成,主要问题是下一次 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

【问题讨论】:

    标签: javascript ecmascript-6 es6-promise


    【解决方案1】:

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

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

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

    然后会这样调用:

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

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

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

    在对一个函数进行 Promise 时,主要要做的是将处理回调和错误条件的脏活封装到一个返回 Promise 的核心函数中。然后,您可以在基于 Promise 的简洁流畅的代码中使用此功能,并允许您在控制流中使用 Promise 真正出色的错误处理功能。

    对于请求项目,看起来您构建了一个 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 这样的 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});
    }    
    

    【讨论】:

    • 谢谢,我用 promises 把我的函数分成了几个。
    • 我重构了代码,放了here,也许你可以检查一下,如果我做得对吗?
    • @VladShcherbin - 它有效吗?我肯定不会将request-promise 的模块句柄命名为promise。这真是令人困惑。将其命名为 requestPrequestPromise 或表明其作用的名称。
    • @VladShcherbin - 在您的代码中 .then(getMore()); 是错误的。应该是.then(getMore);你传递了一个函数引用。
    • @Carr - 在这种情况下它可能会起作用,但这不是一个特别好的做法。通常,当您拒绝时,您不想在该块中运行任何其他代码,因此我使用return 来停止执行其他任何代码。在这种情况下,唯一的其他代码是resolve(),如果您没有return,它将被调用,但由于您已经调用了reject(),它不会做任何事情。我更愿意在代码中明确指出,如果有错误,我想reject() 并且不再运行任何代码。
    猜你喜欢
    • 2021-11-21
    • 1970-01-01
    • 2018-12-15
    • 2015-05-15
    • 2014-06-14
    • 2022-01-04
    • 2017-06-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多