В моем приложении React у меня есть массив параметров (например, некоторые идентификаторы), которые следует использовать в качестве параметров для очереди вызовов ajax. Проблема состоит в том, что массив может содержать более 1000 элементов, и если я просто рекурсивно выполняю вызов ajax с помощью цикла forEach, страница браузера в конечном итоге перестает отвечать до того, как будет решен каждый из запросов.

Существует ли методика / библиотека, которая может позволить отправлять ajax-запросы, например, по 5 запросов за раз асинхронно, и только после того, как они будут завершены, перейти к следующим 5?

Последующие вопросы:

React - Управление несколькими Ajax-вызовами

Реакция - управление вызовами AJAX на сервер

2
Lokesh Agrawal 31 Авг 2017 в 21:43

4 ответа

Лучший ответ

Если вы не ограничены версией es и можете использовать es6, тогда вам стоит посмотреть async await

async function makeBatchCalls(arrayIds, length) {
    let test = arrayIds.reduce(
        (rows, key, index) => (index % length == 0 
            ? rows.push([key]) 
            : rows[rows.length - 1].push(key)
        ) && rows, 
        []
    );

    let Batchresults = [];

    //convert them to two dimensionl arrays of given length [[1,2,3,4,5], [6,7,8,9,10]]
    for (calls of test) {
       Batchresults.push(
           await Promise.all(
               calls.map((call) => fetch(`https://jsonplaceholder.typicode.com/posts/${call}`))
           )
       );
    }

    return Promise.all(Batchresults); //wait for all batch calls to finish
}

makeBatchCalls([1,2,3,4,5,6,7,8,9,10,12,12,13,14,15,16,17,18,19,20],3)

Async / await предназначен для точного сценария ожидания асинхронных вызовов. в основном внутри асинхронной функции, пока ожидание не будет разрешено, выполнение приостанавливается. Вы должны понимать обещания и генераторы, прежде чем начать их использовать.

2
Dimitar Dimitrov 5 Дек 2019 в 10:19

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

// IMPLEMENTATION ####################

// Port of lower_bound from http://en.cppreference.com/w/cpp/algorithm/lower_bound
// Used to compute insertion index to keep queue sorted after insertion
function lowerBound(array, value, comp) {
  let first = 0;
  let count = array.length;

  while (count > 0) {
    const step = (count / 2) | 0;
    let it = first + step;

    if (comp(array[it], value) <= 0) {
      first = ++it;
      count -= step + 1;
    } else {
      count = step;
    }
  }

  return first;
}

class PriorityQueue {
  constructor() {
    this._queue = [];
  }

  enqueue(run, opts) {
    opts = Object.assign({
      priority: 0
    }, opts);

    const element = {
      priority: opts.priority,
      run
    };

    if (this.size && this._queue[this.size - 1].priority >= opts.priority) {
      this._queue.push(element);
      return;
    }

    const index = lowerBound(this._queue, element, (a, b) => b.priority - a.priority);
    this._queue.splice(index, 0, element);
  }

  dequeue() {
    return this._queue.shift().run;
  }

  get size() {
    return this._queue.length;
  }
}

class PQueue {
  constructor(opts) {
    opts = Object.assign({
      concurrency: Infinity,
      queueClass: PriorityQueue
    }, opts);

    if (opts.concurrency < 1) {
      throw new TypeError('Expected `concurrency` to be a number from 1 and up');
    }

    this.queue = new opts.queueClass(); // eslint-disable-line new-cap
    this._queueClass = opts.queueClass;
    this._pendingCount = 0;
    this._concurrency = opts.concurrency;
    this._resolveEmpty = () => {};
    this._resolveIdle = () => {};
  }

  _next() {
    this._pendingCount--;

    if (this.queue.size > 0) {
      this.queue.dequeue()();
    } else {
      this._resolveEmpty();

      if (this._pendingCount === 0) {
        this._resolveIdle();
      }
    }
  }

  add(fn, opts) {
    return new Promise((resolve, reject) => {
      const run = () => {
        this._pendingCount++;

        fn().then(
          val => {
            resolve(val);
            this._next();
          },
          err => {
            reject(err);
            this._next();
          }
        );
      };

      if (this._pendingCount < this._concurrency) {
        run();
      } else {
        this.queue.enqueue(run, opts);
      }
    });
  }

  addAll(fns, opts) {
    return Promise.all(fns.map(fn => this.add(fn, opts)));
  }

  clear() {
    this.queue = new this._queueClass(); // eslint-disable-line new-cap
  }

  onEmpty() {
    // Instantly resolve if the queue is empty
    if (this.queue.size === 0) {
      return Promise.resolve();
    }

    return new Promise(resolve => {
      const existingResolve = this._resolveEmpty;
      this._resolveEmpty = () => {
        existingResolve();
        resolve();
      };
    });
  }

  onIdle() {
    // Instantly resolve if none pending
    if (this._pendingCount === 0) {
      return Promise.resolve();
    }

    return new Promise(resolve => {
      const existingResolve = this._resolveIdle;
      this._resolveIdle = () => {
        existingResolve();
        resolve();
      };
    });
  }

  get size() {
    return this.queue.size;
  }

  get pending() {
    return this._pendingCount;
  }
}


// TEST ####################


const promises = new PQueue({
  concurrency: 4
});

const makePromise = (key, time) => {
  let response = null;
  return new Promise(resolve => {
    setTimeout(() => {
      response = `Promise ${key} resolved`;
      console.log(response);
      resolve(response);
    }, time);
  });
}

promises.add(() => makePromise('p1', 5000));
promises.add(() => makePromise('p2', 1000));
promises.add(() => makePromise('p3', 3000));
promises.add(() => makePromise('p4', 6000));
promises.add(() => makePromise('p5', 2000));
promises.add(() => makePromise('p6', 1500));
promises.add(() => makePromise('p7', 5500));
promises.add(() => makePromise('p8', 7000));

promises.onIdle().then(() => {
  console.log('Promises queue empty.');
});
2
Julio Betta 31 Авг 2017 в 19:34

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

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

0
Nafiul Islam 31 Авг 2017 в 18:49

Хорошо, давайте разберемся с некоторыми вещами. В JavaScript запросы AJAX по своей природе асинхронны. Вы решили сделать их синхронными в своей реализации.

То, что вам нужно сделать, это иметь некоторый массив запросов, из которых вы извлекаете результаты X за раз, ждете их возврата и повторяете.

let ids = [a lot of ids here]

while (ids.length > 0) {

   let c = ids.splice(0, 5)
   let promises = []
   for (let i = 0; i < c.length; i++) {
      promises.push(fetch("someURL").then(function() {}))
   }
   Promise.all(promises)
}

Одновременно выполнит 5 запросов, дождется их завершения, затем получит следующую часть идентификаторов

4
Alexey Soshin 31 Авг 2017 в 18:57