神族九帝's blog 神族九帝's blog
首页
网盘 (opens new window)
线报 (opens new window)
商城 (opens new window)
  • 复习指导
  • HTML
  • CSS
  • JavaScript
  • 设计模式
  • 浏览器
  • 手写系列
  • Vue
  • Webpack
  • Http
  • 前端优化
  • 项目
  • 面试真题
  • 算法
  • 精选文章
  • 八股文
  • 前端工程化
  • 基础篇
  • 进阶篇
  • 高级篇
  • 计算机基础
  • 高频考点
  • 精简题
  • 综合问题
  • 复习题
  • 导航
  • vue
  • vue2源码学习
  • 剖析vuejs内部运行机制
  • TypeScript 入门实战笔记
  • vue3源码学习
  • npm发包
  • 重学node
  • 前端性能优化方法与实战
  • webpack原理与实战
  • webGl
  • 前端优化
  • Web3
  • 更多
  • 网站
  • 资源
  • Vue资源
  • 收藏的一些API
  • 未来要做的事
  • 宝塔面板+青龙面板
  • 安卓手机当服务器使用
  • 京东自动评价代码
  • 搭建x-ui免流服务器(已失效)
  • 海外联盟
  • 好玩的docker
  • 导航
GitHub (opens new window)

神族九帝,永不言弃

首页
网盘 (opens new window)
线报 (opens new window)
商城 (opens new window)
  • 复习指导
  • HTML
  • CSS
  • JavaScript
  • 设计模式
  • 浏览器
  • 手写系列
  • Vue
  • Webpack
  • Http
  • 前端优化
  • 项目
  • 面试真题
  • 算法
  • 精选文章
  • 八股文
  • 前端工程化
  • 基础篇
  • 进阶篇
  • 高级篇
  • 计算机基础
  • 高频考点
  • 精简题
  • 综合问题
  • 复习题
  • 导航
  • vue
  • vue2源码学习
  • 剖析vuejs内部运行机制
  • TypeScript 入门实战笔记
  • vue3源码学习
  • npm发包
  • 重学node
  • 前端性能优化方法与实战
  • webpack原理与实战
  • webGl
  • 前端优化
  • Web3
  • 更多
  • 网站
  • 资源
  • Vue资源
  • 收藏的一些API
  • 未来要做的事
  • 宝塔面板+青龙面板
  • 安卓手机当服务器使用
  • 京东自动评价代码
  • 搭建x-ui免流服务器(已失效)
  • 海外联盟
  • 好玩的docker
  • 导航
GitHub (opens new window)
  • 复习指导

  • HTML

  • CSS

  • JavaScript

  • 设计模式

  • 浏览器

  • 手写系列

    • 继承
    • call,apply,bind
    • 手写promise
    • 手写基础js
    • 手写排序
    • 并行限制的Promise
      • 类
      • 函数
      • 模拟请求
        • promise.all
      • 参考链接
  • Vue

  • Webpack

  • Http

  • 前端优化

  • 项目

  • 面试真题

  • 算法

  • 精选文章

  • 八股文

  • 前端工程化

  • 面试题
  • 手写系列
wu529778790
2022-01-18

并行限制的Promise

知耻而后勇

# 类

JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个。完善下面代码的 Scheduler 类,使以下程序能够正常输出:

class Scheduler {
  constructor() {
    this.queue = [];
    this.maxCount = 2;
    this.runCounts = 0;
  }
  add(promiseCreator) {
    this.queue.push(promiseCreator);
  }
  taskStart() {
    for (let i = 0; i < this.maxCount; i++) {
      this.request();
    }
  }
  request() {
    if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
      return;
    }
    this.runCounts++;

    this.queue
      .shift()()
      .then(() => {
        this.runCounts--;
        this.request();
      });
  }
}

const timeout = (time) =>
  new Promise((resolve) => {
    setTimeout(resolve, time);
  });

const scheduler = new Scheduler();

const addTask = (time, order) => {
  scheduler.add(() => timeout(time).then(() => console.log(order)));
};

addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");

// output: 2 3 1 4

可以看到,最多时存在两个并行的 Promise,并且一个 Promise 执行完成之后,执行新的 Promise,并且新执行的 Promise 不会影响到另一个正在执行的 Promise。

既然如此的话,就不能使用 Promise.all()和 Promise.race()这两个 API 了,Promise.all()会等待所有 Promise 完成,Promise.race()只会执行一个 Promise。

其实从 Promise 依序进行执行,可以使用队列先进先出的特性,add 操作知识每次用队列中插入 Promise Creator,判断当前执行数量是否小于 2,如果小于 2 就从队列中弹出 Promise Creator 执行并给执行的 Promise 绑定 then 函数,then 函数被调用就说明当前 Promise 已经执行完成,重复当前操作,可以看出是一个递归的操作。

# 函数

function limitRequest(limit, requestes) {
  let runCount = 0;
  const request = () => {
    if (!requestes || !requestes.length || runCount >= limit) {
      return;
    }
    runCount++;
    requestes
      .shift()()
      .then(() => {
        runCount--;
        request();
      });
  };
  for (let i = 0; i < limit; i++) {
    request();
  }
}
const timeout = (time) =>
  new Promise((resolve) => {
    setTimeout(resolve, time);
  });
const requestes = [
  () => timeout(1000).then(() => console.log(11)),
  () => timeout(500).then(() => console.log(22)),
  () => timeout(300).then(() => console.log(33)),
  () => timeout(400).then(() => console.log(44)),
];
limitRequest(2, requestes);

# 模拟请求

当我们需要保证代码在多个异步操作都完成后执行,通常我们会使用 Promise.all 来实现。以请求多张图片为例:

// 为了演示方便,我们在此用fetchImage函数来模拟异步请求图片,返回成功提示
function fetchImage(url) {
  // 模拟请求的响应时间在0 - 1s之间随机
  const timeCost = Math.random() * 1000;
  return new Promise((resolve) => setTimeout(resolve, timeCost, "get: " + url));
}
// 待请求的图片
const imageUrls = [
  "pic_1.png",
  "pic_2.png",
  "pic_3.png",
  "pic_4.png",
  "pic_5.png",
  "pic_6.png",
];
Promise.all(imageUrls.map((url) => fetchImage(url))).then((resList) =>
  console.log(resList)
);

如果我们对并行的请求数量有限制,Promise.all 自身是不具有这个功能的。 那么接下来,我们依旧以上述的 fetchImage 为例,来实现一个可以对图片请求进行并行限制的函数:

/**
 * @description 带并发限制的图片并发请求
 * @param {Array} imageUrls 待请求的图片url列表
 * @param {Object} limit 最大并发个数限制
 * @return { Promise<Array> } resList
 */
function fetchImageWithLimit(imageUrls, limit = 4) {}

# promise.all

  1. 初始化 limit 个 Promise 对象,作为 Promise.all 的参数
  2. 每个 Promise 对象去 imageUrls 中取出一个 url 进行请求,若无则 resolve
  3. 每个 Promise 对象在当前请求成功后重复步骤 2
/**
 * @description 带并发限制的图片并发请求
 * @param {Array} imageUrls 待请求的图片url列表
 * @param {Object} limit 最大并发个数限制
 * @return { Promise<Array> } resList
 */
function fetchImageWithLimit(imageUrls, limit = 2) {
  let urls = [...imageUrls];
  let rs = new Map();
  function run() {
    if (urls.length > 0) {
      const url = urls.shift();
      console.log(url, " [start at] ", new Date().getTime() % 10000);
      return fetchImage(url).then((res) => {
        console.log(url, " [end at] ", new Date().getTime() % 10000);
        rs.set(url, res);
        return run();
      });
    }
  }
  const promiseList = Array(Math.min(limit, imageUrls.length))
    .fill(Promise.resolve())
    .map((promise) => promise.then(run));
  return Promise.all(promiseList).then(() =>
    imageUrls.map((item) => rs.get(item))
  );
}
fetchImageWithLimit(imageUrls).then((res) => console.log(res));

# 参考链接

  • https://juejin.cn/post/6854573217013563405 (opens new window)
  • https://juejin.cn/post/6844904115487637517 (opens new window)
  • https://juejin.cn/post/7041515221625602055 (opens new window)
编辑 (opens new window)
上次更新: 2022/02/09, 18:33:18
手写排序
vue组件传值方式

← 手写排序 vue组件传值方式→

最近更新
01
导航
07-05
02
好玩的docker
07-04
03
我的第一个NFT
07-03
更多文章>
Power by vuepress | Copyright © 2015-2022 神族九帝
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×