前端关于API请求控制的总结

本文探讨了并发控制在高并发场景下的应用,包括使用Promise实现的请求池控制、取消请求的AbortController API,以及节流和淘汰请求的策略。重点讲解了如何通过并发限制、信号控制和发布订阅模式提高资源效率。
摘要由CSDN通过智能技术生成

并发控制

对于短时间可能发送大量网络请求的场景,为了节约资源,需要进行请求的并发控制。设置最大并发数,当某个请求完成时,才发起新的请求:

/**
 * 请求并发控制
 * @param {*} requestPool 请求池 (是一个可迭代对象)
 * @param {*} poolLimit 最大并发数
 */
/** es7 */
async function concurrencyControl(requestPool, poolLimit) {
    /** 用于请求的结果 */
    const ret = [];
    /** 真正并发执行的请求集合 */
    const executing = new Set();
    for (const item of requestPool) {
        /** 防止返回的不是promise,使用Promise.resolve */
        const p = Promise.resolve().then(() => item());
        /** 将正在请求的promise放入ret和executing中 */
        ret.push(p);
        executing.add(p);
        /** 请求resolve 或 reject 执行 清除操作  */
        const clean = () => executing.delete(p);
        p.then(clean).catch(clean);
        if (executing.size >= poolLimit) {
            // 一旦正在执行的promise列表数量等于限制数,就使用Promise.race等待某一个promise状态发生变更,
            // 状态变更后,就会执行上面then的回调,将该promise从executing中删除,
            // 然后再进入到下一次for循环,生成新的promise进行补充
            await Promise.race(excuting);
        }
    }
    return Promise.all(ret);
}

/** es6 */
function concurrencyControlES6(requestPool, poolLimit) {
    let i = 0;
    const ret = [];
    const executing = new Set();
    const enqueue = function() {
      if (i === requestPool.length) {
        return Promise.resolve();
      }
      const item = iterable[i++];
      const p = Promise.resolve().then(() => item());
      ret.push(p);
      executing.add(p);
      const clean = () => executing.delete(p);
      p.then(clean).catch(clean);
      let r = Promise.resolve();
      if (executing.size >= poolLimit) {
        r = Promise.race(executing);
      }
      return r.then(() => enqueue());
    };
    return enqueue().then(() => Promise.all(ret));
}

async/await 是 ES7 的特性,用 ES6 也能实现相同的效果,而且ES6的方式的可以动态的添加新的请求:

function main() {
  concurrentControlES6(requestPool, 2).then(res => console.log(res));
  /** 动态添加新请求 */
  requestPool.push(newRequest());
}

取消请求

想实现真正的取消请求,就要用到 AbortController API:

const abortController = new AbortController();
const signal = abortController.signal;

setTimeout(() => abortController.abort(), 5000);

fetch(url, { signal }).then(response => {
  return response.text();
}).then(text => {
  console.log(text);
}).catch(err => {
  if (err.name === 'AbortError') {
    console.log('Fetch aborted');
  } else {
    console.error('Uh oh, an error!', err);
  }
});

当调用 abort() 时,promise 会被 reject 掉,触发一个名为 AbortError 的 DOMException。 

节流控制

通过发布订阅的设计模式,对请求的结果进行复用,适用于在短时间内发送多个相同请求的场景。

关键在于如果有正在进行的请求,则新建一个 promise,将 resolve 和 reject 存到 listeners 数组中,订阅请求的结果。:

function reqThrottler(delay) {
  /** 是否正在请求中 */
  let isReqing = false;
  /** 订阅请求 resolve 和 reject 的数组*/
  const listeners = [];

  return (query, request) => {
    /** 正在请求中,新建一个promise存到listerers中 */
    if (isReqing) {
        return new Promise((resolve, reject) => {
            listeners.push({ resolve, reject });
        })
    }
    isReqing = true;
    return new Promise(resolve => {
        /** 正在请求中... */
        const p = Promise.resovle().then(() => request(query));
        let res;
        p.then((value) => {
            res = value;
            return resolve(value);
        });
        setTimeout(() => {
            isReqing = false;
            if (listeners.length <= 0) return;
            while (listeners.length > 0) {
                const listener = listeners.shift();
                listener && listener.resolve(res);
            }   
        }, delay);
    })
  }
}

淘汰请求

根据搜索词展示关联词的场景,短时间会发送大量的请求,这时我们要保证先发起的请求如果后返回是需要淘汰掉的。我们可以通过比较请求返回时,请求的序号是不是比上一个有效请求大。如果不是,则说明一个后面发起的请求先响应了,当前的请求应该丢弃:

const reqDisuser = () => {
    /**上一个有效请求的序号 */
    let preId = 0;
    /** 请求序号 */
    let seqenceId = 0;
    return (query, requestFn) => new Promise((resovle, reject) => {
        const resolveHandler = (value) => {
            /** 成功返回,只取比上一个有效请求大的结果 */
            if(seqenceId > preId) {
               preId = seqenceId;
               resolve(value);
            } else {
               reject(`被淘汰的请求, 请求序号=${seqenceId}`)
            };
        }
        const rejectHandler = (err) => {
            reject(err);
        }
        const p = Promise.resolve().then(() => {
             /** 发起一个请求时,序号加 1 */
            seqenceId = seqenceId + 1;
            return requestFn(query);
        });
        p.then(resolveHandler).catch(rejectHandler);
    })
}

我们就可以判断 requestFn 返回结果是否正确,来决定是否展示,因为淘汰的请求被reject掉了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛定谔的猫96

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值