Вызов Api с помощью обещаний javascript в рекурсии


Я хотел бы использовать Gitter api, чтобы получить все сообщения из комнаты.

Что мне нужно сделать, так это отправить запрос get api, например, с 50 элементами, onComplete мне нужно отправить другой запрос с 50 элементами и пропустить 50 элементов, которые я уже получил. Делайте этот запрос до тех пор, пока они не вернут какие-либо предметы. Итак:

  • Отправить запрос api
  • json разобрать его
  • : запрос содержит элементы
    • Сделайте sql-запрос с этими элементами
    • продолжить запрос
    • Отправить следующий api запрос (рекурсия?)
    • ? если в следующем запросе api больше нет элементов-показать сообщение done
  • : запрос не содержит элементов
    • прервать сообщение
Я пробую обещания для этого, но я немного запутался в них и не знаю, правильно ли я все делаю. Основная проблема заключается в следующем вызове 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 4

1 ответ:

Ваш код меня сильно запутал. Самый простой способ использовать обещания с асинхронными операциями - "обещать" существующие асинхронные операции, а затем написать всю логику после этого, используя обещания. "Обещать" что-то означает генерировать или писать функцию-оболочку, которая возвращает обещание, а не использует только обратный вызов.

Во-первых, давайте рассмотрим общую логику. В соответствии с вашим вопросом, Вы сказали, что у вас есть 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();        
}
Этот код использует цепочку обещаний, которая является немного продвинутой, но чрезвычайно полезной функцией обещаний. Когда вы возвращаете обещание от обработчика .then(), оно привязывается к предыдущему обещанию, автоматически связывая их все вместе в серию операций. Конечный результат возврата или ошибка затем возвращаются обратно через исходное обещание исходному вызывающему объекту. Аналогично, любая ошибка, которая может произойти в этой цепочке, передается обратно к исходному вызывающему объекту. Это чрезвычайно полезно в сложных функциях с несколькими асинхронными операциями, где вы не можете просто вернуть или бросьте, если используете регулярные обратные вызовы.

Тогда это будет называться так:

getAllItems(this.room, 50, config.token).then(function() {
    // finished successfully here
}, function(err) {
    // had an error here
});
Теперь я поработаю над некоторыми примерами для созданных многообещающих версий ваших вызовов нижних уровней для реализации getItems() и storeItems(). Сейчас вернусь с ними.

Я не вполне понимаю все детали ваших асинхронных операций, поэтому это не будет полностью рабочий пример, но должен проиллюстрировать общую концепцию, и вы можете задать любые необходимые уточняющие вопросы о реализация.

При обещании функции главное, что вы хотите сделать, - это инкапсулировать грязную работу обработки обратного вызова и условий ошибки в одну основную функцию, которая возвращает обещание. Это затем позволяет использовать эту функцию в хорошем чистом потоковом коде на основе обещаний и позволяет использовать действительно большие возможности обработки ошибок обещаний в потоке управления.

Для запроса элементов, похоже, вы создаете URL-адрес, который занимает кучу аргументы в URL-адресе, и вы получите результаты обратно в JSON. Я предполагаю, что это узел.JS окружающая среда. Итак, вот как вы можете выполнить реализацию getItems() с помощью узла.модуль js request(). Это возвращает обещание, разрешенное значение которого будет уже проанализированным объектом 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);
        });
    });
}    

Для хранения предметов мы хотим сделать то же самое. Мы хотим создать функцию, которая принимает данные для хранения в качестве аргументов и возвращает обещание и он делает всю грязную работу внутри функции. Итак, логически вы хотите иметь такую структуру:

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

Извините, но я не совсем понимаю, что вы делаете с вашей базой данных mySql достаточно, чтобы полностью заполнить эту функцию. Надеюсь, эта структура даст вам достаточно идей, как закончить его или задать несколько вопросов, если вы застряли.


Примечание: Если вы используете библиотеку Promise, такую как Bluebird, чтобы добавить дополнительную функциональную функциональность promise, то promisifying существующая операция-это то, что встроено в 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});
}