简体   繁体   English

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

[英]Make an Api call with javascript promises in recursion

I would like to use the gitter api to get all the messages from a room. 我想使用gitter api从房间获取所有消息。

What I need to do is to send the get api request with eg 50 items, onComplete I need to send another request with 50 items and skip 50 items I already received. 我需要做的是发送get api请求,例如50个项目,onComplete我需要发送另外50个项目的请求,并跳过我已经收到的50个项目。 Do this request till they won't return any items. 请求此请求,直到他们不会退回任何项目。 So: 所以:

  • send api request 发送api请求
  • json parse it 杰森解析它
  • : request has items :请求有项目
    • make a sql query with this items 使用此项进行SQL查询
    • proceed the query 继续查询
    • send next api request (recursion?) 发送下一个api请求(递归?)
    • ? if no more items in next api request - show done message 如果下一个api请求中没有更多项目 - 显示完成消息
  • : request does not have items :请求没有项目
    • abort with message 中止消息

I am trying Promises for that, but I got a bit confused with them and don't know, if I am doing everything right. 我正在尝试Promise,但我对他们有点困惑,不知道,如果我做的一切正确。 The main problem is the next Api call and callback if all calls are done. 主要问题是如果所有呼叫都完成,则下一个Api呼叫和回叫。 This is my code: 这是我的代码:

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);

Maybe you can check and help me? 也许你可以检查并帮助我? Or provide similar code for such things? 或者为这些东西提供类似的代码?

update 更新

Here is what I refactored the code to: jsfiddle 以下是我重构代码的内容: jsfiddle

Your code has me pretty confused. 你的代码让我非常困惑。 The simplest way to use promises with async operations is to "promisify" your existing async operations and then write all the logic after that using promises. 将promises与异步操作一起使用的最简单方法是“保证”现有的异步操作,然后使用promises编写所有逻辑。 To "promisify" something means to generate or write a wrapper function that returns a promise rather than uses only a callback. “promisify”意味着生成或编写一个返回promise而不是仅使用回调的包装函数。

First, lets look at the overall logic. 首先,让我们看一下整体逻辑。 Per your question, you said you have an API that you want to call to fetch 50 items at a time until you've gotten them all. 根据你的问题,你说你有一个API,你想要一次性获取50个项目,直到你得到它们为止。 That can be done with a recursive-like structure. 这可以通过类似递归的结构来完成。 Create an internal function that does the retrieving and returns a promise and each time it completes call it again. 创建一个内部函数,执行检索并返回一个promise,每次完成时再次调用它。 Assuming you have two core functions involved here, one called getItems() that gets items from your API and returns a promise and one called storeItems() that stores those items in your database. 假设这里涉及两个核心函数,一个名为getItems() ,它从API获取项并返回一个promise,一个名为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();        
}

This code makes use of chaining promises which is a slightly advanced, but extremely useful feature of promises. 此代码使用链接承诺,这是承诺的稍微高级但非常有用的功能。 When you return a promise from a .then() handler, it is chained onto the previous promise, automatically linking them all together into a series of operations. 当您从.then()处理程序返回一个promise时,它会链接到前一个promise,自动将它们全部链接到一系列操作中。 The final return result or error is then returned back through the original promise to the original caller. 然后,最终的返回结果或错误将通过原始承诺返回给原始调用者。 Similarly, any error that might happen in this chain is propagated all the way back to the original caller. 同样,此链中可能发生的任何错误都会一直传播回原始调用方。 This is extremely useful in complicated functions with multiple async operations where you cannot just simply return or throw if using regular callbacks. 这在具有多个异步操作的复杂函数中非常有用,如果使用常规回调,则不能简单地返回或抛出。

This would then be called like this: 然后将这样调用:

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

Now, I'll work on some examples for created promisified versions of your lower levels calls to implement getItems() and storeItems() . 现在,我将研究一些示例,用于创建低级调用的promisified版本,以实现getItems()storeItems() Back in a moment with those. 回到那一刻。

I don't quite fully understand all the details in your async operations so this will not be a fully working example, but should illustrate the overall concept and you can then ask any necessary clarifying questions about the implementation. 我并不完全了解您的异步操作中的所有细节,因此这不是一个完整的示例,但应该说明整体概念,然后您可以询问有关实现的任何必要的澄清问题。

When promisifying a function, the main thing you want to do is to encapsulate the dirty work of handling the callback and error conditions into one core function that returns a promise. 在宣传函数时,您要做的主要事情是将处理回调和错误条件的脏工作封装到一个返回promise的核心函数中。 That then allows you to use this function in nice clean flowing promise-based code and allows you to use the really great error handling capabilities of promises in your control flow. 然后,您可以在基于承诺的良好代码中使用此函数,并允许您在控制流中使用promises的非常好的错误处理功能。

For requesting the items, it looks like you construct a URL that takes a bunch of arguments in the URL and you get the results back in JSON. 对于请求项目,看起来您构造了一个URL,该URL在URL中获取了大量参数,并以JSON形式返回结果。 I'm assuming this is a node.js environment. 我假设这是一个node.js环境。 So, here's how you could do the getItems() implementation using the node.js request() module. 因此,以下是使用node.js request()模块执行getItems()实现的方法。 This returns a promise whose resolved value will be the already parsed Javascript object representing the results of the api call. 这将返回一个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);
        });
    });
}    

For storing the items, we want to accomplish the same thing. 为了存储物品,我们想要完成同样的事情。 We want to create a function that takes the data to store as arguments and returns a promise and it does all the dirty work inside the function. 我们想创建一个函数,它将数据作为参数存储并返回一个promise,它完成函数内部的所有脏工作。 So logically, you want to have this structure: 从逻辑上讲,你想拥有这样的结构:

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

Sorry, but I don't quite understand what you're doing with your mySql database enough to fully fill out this function. 对不起,但我不太明白你在mySql数据库中做了什么,足以完全填写这个功能。 Hopefully this structure gives you enough of an idea how to finish it off or ask some questions if you get stuck. 希望这个结构能够让你充分了解如何完成它或者如果你遇到问题就提出一些问题。


Note: if you use a Promise library like Bluebird to add additional promise functional functionality, then promisifying an existing operation is something that is built into Bluebird so getItems() just becomes this: 注意:如果你使用像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});
}    

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM