JavaScript、React - 发送多个同步 ajax 调用

JavaScript, React - sending multiple simultaneous ajax calls

在我的 React 应用程序中,我有一个参数数组(例如一些 ID),它们应该用作 ajax 调用队列的参数。问题是数组可能包含超过 1000 个项目,如果我只是使用 forEach 循环递归地进行 ajax 调用,浏览器页面最终会在每个请求得到解决之前停止响应。

是否有 technique/library,可以允许发送 ajax 请求,例如,一次异步发送 5 个请求,并且仅当这些请求完成后,才继续下 5 个请求?

跟进问题:

好的,让我们解决一些问题。在 JavaScript AJAX 中,请求本质上是异步的。您选择在您的实现中使它们在某种程度上同步。

您需要做的是有一些请求数组,一次从中弹出 X 个结果,等待它们 return,然后重复。

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 个请求,等待它们完成,然后获取下一部分 ID

在这种情况下,最好更改后端,您可以在后端处理数千个输入的结果,一次发送它们,而不是调用一千次。另一种方法是使用 promise 我认为。

您也可以看看this link是否适合您。我想这些东西可以回答你的问题

我在一个项目中遇到了同样的问题。您需要的是一个优先级队列,以控制同时执行多少个请求。我正在使用 this library。由于 p-queue 的实现非常简单易懂,而且并不大,我将代码粘贴到下面的代码片段中只是为了向您展示它在最新行中的工作原理。

// 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.');
});

如果你不受 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 适用于等待异步调用的确切场景。基本上在 async 函数内部,直到 await 被解决,执行被暂停。在开始使用它们之前,您需要了解承诺和生成器。