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

IT技术 javascript ajax reactjs redux axios
2021-04-23 22:19:32

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

是否有一种技术/库,它可以允许发送 ajax 请求,例如,一次异步发送 5 个请求,并且只有当这些请求完成后,才能继续下一个 5 个请求?

跟进问题:

React - 控制多个 Ajax 调用

React - 控制对服务器进行的 AJAX 调用

4个回答

好的,让我们整理一些事情。在 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 个请求,等待它们完成,然后获取下一部分 ID

如果您不受 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 执行被挂起。在开始使用它们之前,您需要了解 Promise 和生成器。

我在一个项目中遇到了同样的问题。您需要的是一个优先级队列,以便控制将同时执行的请求数量。我正在使用这个库由于 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.');
});

在这种情况下,最好在后端进行更改,在那里您可以处理数千个输入的结果,而不是从一千次调用。另一种方法是使用我认为的Promise

如果它适用于您,您也可以查看此链接我认为这些事情回答了你的问题