notes
  • Introduction
  • 语言
    • JS
      • JS原型到原型链
      • JS继承的实现
      • this对象
      • Promise基本概念
      • Promise实现
      • Promise实战
      • JS的EventLoop
      • JS容易出现误区的运算符
      • JS容易出现误区的操作符
      • JS深拷贝
      • JS节流与防抖
      • ES5实现原生/ES6方法
    • TS
      • 声明文件
      • 项目配置
    • CSS
      • CSS引入方式以及优先级计算
      • BFC神奇背后的原理-文摘
      • 回流reflow与重绘repaint
      • 三栏式布局
      • 垂直居中
      • 清除浮动的方法
      • 移动端适配方案
      • 纯CSS斜切角实现
      • CSS揭秘
      • 背景图片轮播
      • CSS绘制序列帧动画
      • transform实现一个多面体
    • HTML
  • ES6
  • Node.js
    • xxx
  • 前端框架
    • Vue.js
      • Vue双向数据绑定原理
      • Vue-Router原理及实现
    • React
    • AngularJS
  • 工程构建
    • Webpack
      • 01-webpack概述及背景
      • 03-webpack热更新原理
      • 04-splitChunks原理分析
      • 05-webpack工作流程
      • 07-webpack构建流程
      • 07-webpack构建流程
      • 10-webpack 如何进行性能分析
    • Gulp
    • Lint
      • ESLint
      • TSLint
    • Bable
  • 工程化
    • 模块化
    • 组件化
    • 规范化
      • 编码规范
    • 自动化
  • 运维
    • Nginx
    • GIT
    • CDN
    • VPS
    • DBA
  • 小程序
  • 跨端
  • 测试
  • 计算机技术
    • 计算机网络
      • 同源策略到前端跨域解决方案
      • TCP数据传输
      • TCP和UDP的区别
      • HTTP协议概述
      • HTTP缓存详解
      • HTTPS以及SSL建立过程
      • HTTPS的七个误解(译文)
      • cookie与Session机制区别以及Cookie SessionStorage LocalStorage异同
      • HTTP状态码Status
      • DNS原理入门
      • 网络攻击与防御
      • HTTP转发与重定向
      • 登录认证设计
    • 操作系统
      • shell脚本命令
    • Linux
      • Linux命令大全
      • vim命令
    • 浏览器
      • 浏览器内核、JS 引擎、页面呈现原理及其优化
      • 九种浏览器端缓存机制概览
      • 性能优化-网络请求方面
      • webkit解析CSS过程详解
  • 前端面试
    • 算法
Powered by GitBook
On this page
  • Promise 实现串行
  • Promise 并发缓存
  • promise 并发排队
  • Promise 错误捕获
  • Promise的微任务
  • 参考
  1. 语言
  2. JS

Promise实战

Promise 实现串行

要求: 有一组异步请求 apis = [ url1, url2, url3, ...], 用Promise的方式实现串行调用。

这个场景和tapable的 异步串行 类似,使用方式就是p.then().then()..., 核心是在thenable里面返回一个新的promise,这样就能一直执行。

const p = Promise.resolve();
apis.forEach(api => {
    p = p.then(fetch(api));
});

p.then(res1).then(res2)...

通过reduce实现

const p = apis.reduce((promise,api) => promise.then(fetch(api)),Promise.resolve())

Promise 并发缓存

场景:现有一批相同的并发查询请求,希望只查询一次SQL,期间的请求走缓存,使用promise怎么实现。

const sqlResult = 'sql result';
let cacheDate = '';
let cachePromise = null;
function SQL() {
  if (cacheDate) return cacheDate;
  if (cachePromise) return cachePromise;
  cachePromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('只执行一次');
      resolve(sqlResult);
      cacheDate = sqlResult;
      cachePromise = null;
    }, 1000);
  });

  return cachePromise;
}

for (let i = 0; i < 10; i++) {
  SQL().then((res) => {
    console.log(res);
  });
}

promise 并发排队

场景:有多个图片资源的url,已经存储在数组urls中,而且已经有一个函数loadImg,输入一个url链接,返回一个 Promise,该Promise在图片下载完成的时候resolve,下载失败则reject。任意时刻,同时下载的链接数量不可以超过3个,使用最快的速度将图片下载完毕。

const urls = [
  'https://img01.yzcdn.cn/upload_files/2021/11/30/Fqu72DfPGmYyqRzMWwQDtYVWz2iG.png',
  'https://img01.yzcdn.cn/upload_files/2021/11/30/FvDLcro_xAmCWbVH4porfft6zonI.png',
  'https://img01.yzcdn.cn/upload_files/2021/11/30/FnM-mP1mfIggWWFAKJNusWm1mlKz.png',
  'https://img01.yzcdn.cn/upload_files/2021/11/23/FixGtb4CeBbW_3k6GLfP0hVhgRfp.png',
  'https://img01.yzcdn.cn/upload_files/2021/11/23/Fm9oZBZJgdn2LI2COk5Ry7hhcMhV.png',
  'https://img01.yzcdn.cn/upload_files/2021/11/23/Fvh0GfzNePBlKjGXB7onoJqekRN_.png',
  'https://img01.yzcdn.cn/upload_files/2021/11/23/Fosi97bMcPb2SfxnoN8di72U7G8b.png',
  'https://img01.yzcdn.cn/upload_files/2021/11/17/FpxGbdBBzOpa8uuQlGCo0-Ggo44K.png',
  'https://img01.yzcdn.cn/upload_files/2021/11/17/FihbkMX1X3Q1YMFHyykZWd7X0T4k.png',
  ];

function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () =>{
      console.log('图片load完成')
      resolve();
    };
    img.error = reject;
    img.src = url;
  });
} 

实现如下, 关键点在于使用了Promise.race()检测哪一个最先执行完,执行完返回一个promise,使用链式调用将剩下的串起来

function limitLoad(urls, handler, limit) {
  // 对数组做一个拷贝
  const sequence = [].concat(urls)
  let promises = [];

  // 首先并发请求到最大数
  promises = sequence.splice(0, limit).map((url, index) => {
    // 这里返回的 index 是任务在 promises 的脚标,
    //用于在 Promise.race 之后找到完成的任务脚标
    return handler(url).then(() => {
      return index
    });
  });

  // 循环检查
  (function loop() {
    let p = Promise.race(promises);
    for (let i = 0; i < sequence.length; i++) {
      // promise 链式调用串起来
      p = p.then((res) => {
        promises[res] = handler(sequence[i]).then(() => {
          return res
        });
        return Promise.race(promises)
      })
    }
  })()
}
limitLoad(urls, loadImg, 3)

Promise 错误捕获

下面这段代码会如何输出:

const promise = new Promise(function (resolve, reject) {
  resolve('ok');
  setTimeout(function () { throw new Error('test') }, 0)
});
promise.then(function (value) { console.log(value) });

在resolve之后,promise的状态已经结束了,因为错误在setTimeout,属于下一次的事件循环,所以这里会被抛出,不会被promise吃掉。因此输出结果是

// ok
// Uncaught Error: test

Promise的微任务

Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve(4);
}).then((res) => {
    console.log(res)
})

Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() =>{
    console.log(6);
})

参考

PreviousPromise实现NextJS的EventLoop

Last updated 1 year ago

因此,基于这种场景(排队并发),我们可以设计一个通用的方法。

参考
从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节
Promise 面试题,你能做对几道