Zwx's blog Zwx's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 工作笔记
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档

Zwx

Aal izz well 一切顺利
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 工作笔记
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
  • 技术文档

  • GitHub技巧

  • Nodejs

  • 博客搭建

  • 工作笔记

  • 模板

  • 日常记录

    • Vue 报错Error error0308010Cdigital envelope routinesunsupported
    • Mac版微信突然显示音频设备启动失败
    • Mac OS 版迅雷,不限速下载,破解
    • 记录Vuepress的一些小问题
    • Mac OS版本Infuse播放器,破解
    • 编译原理期末复习公式
    • 手写简单的Promise、PromiseAll、PromiseRace
      • Vue3 设置动态路由出现Error Unknown variable dynamic import
      • $eventBus 的使用、问题
    • 技术
    • 日常记录
    Zwx
    2023-10-17
    0

    手写简单的Promise、PromiseAll、PromiseRace

    # Js手写简单的Promise、Promise.all、Promise.race

    最近准备面试,看八股文手撕Promise,想着也没没事,就看看怎么实现,不得不说现在前端真的卷~~ 全部基本都是转载一位大佬的,这里只简单的整理、实现了Promise、Promise.all、Promise.race,复杂的请移步大佬文章:原文链接 (opens new window),现在找个实习工作都这么难找吗,难道真的是人们说的前端已死???

    # Promise

    • 首先我们在调用 Promise 时,会返回一个 Promise 对象。
    • 构建 Promise 对象时,需要传入一个 executor 函数,Promise 的主要业务流程都在 executor 函数中执行。
    • 如果运行在 excutor 函数中的业务执行成功了,会调用 resolve 函数;如果执行失败了,则调用 reject 函数。
    • Promise 的状态不可逆,同时调用 resolve 函数和 reject 函数,默认会采取第一次调用的结果。

    以上简单介绍了 Promise 的一些主要的使用方法,结合 Promise/A+ (opens new window) 规范,我们可以分析出 Promise 的基本特征:

    1. promise 有三个状态:pending,fulfilled,or rejected;「规范 Promise/A+ 2.1」
    2. new promise时, 需要传递一个executor()执行器,执行器立即执行;
    3. executor接受两个参数,分别是resolve和reject;
    4. promise 的默认状态是 pending;
    5. promise 有一个value保存成功状态的值,可以是undefined/thenable/promise;「规范 Promise/A+ 1.3」
    6. promise 有一个reason保存失败状态的值;「规范 Promise/A+ 1.5」
    7. promise 只能从pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变;
    8. promise 必须有一个then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;「规范 Promise/A+ 2.2」
    9. 如果调用 then 时,promise 已经成功,则执行onFulfilled,参数是promise的value;
    10. 如果调用 then 时,promise 已经失败,那么执行onRejected, 参数是promise的reason;
    11. 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调onRejected;

    按照上面的特征,我们试着勾勒下 Promise 的形状:

    class Promise {
      constructor (excutor) {
        // 规定状态
        this.state = 'peding'
        // 保存 `resolve(res)` 的res值
        this.value = undefined
        // 保存 `reject(err)` 的err值
        this.reason = undefined
    
        // 调用此方法就是成功
        let resolve = (value) => {
          // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
          if (this.state === 'peding') {
            this.state = 'fulfilled'
            this.value = value
          }
        }
    
        // 调用此方法就是失败
        let reject = (reason) => {
          // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
          if (this.state === 'peding') {
            this.state = 'rejected'
            this.reason = reason
          }
        }
    
        try {
          // 立即执行,将 resolve 和 reject 函数传给使用者  
          excutor(resolve, reject)
        } catch (error) {
          // 若出错,直接调用reject
          reject(error)
        }
      }
    
      // 包含一个 then 方法,并接收两个参数 onFulfilled、onRejected
      then (onFulfilled, onRejected) {
        if (this.state === 'fulfilled') {
          onFulfilled(this.value)
        }
    
        if (this.state === 'rejected') {
          onRejected(this.reason)
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47

    写完代码我们可以测试一下:

    let p1 = new Promise((resolve, reject) => {
        resolve('ok1');
    }).then(res => {
      console.log(res);
    })
    
    1
    2
    3
    4
    5

    控制台输出:

    ok1
    
    1

    现在我们已经实现了一个基础版的 Promise,但是还不要高兴的太早噢,这里我们只处理了同步操作的 promise。如果在 executor()中传入一个异步操作的话呢,我们试一下:

    let p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('ok2')
      }, 1000);
    }).then(res => {
      console.log(res);
    })
    
    1
    2
    3
    4
    5
    6
    7

    执行测试脚本后发现,promise 没有任何返回。

    因为 promise 调用 then 方法时,当前的 promise 并没有成功,一直处于 pending 状态。所以如果当调用 then 方法时,当前状态是 pending,我们需要先将成功和失败的回调分别存放起来,在executor()的异步任务被执行时,触发 resolve 或 reject,依次调用成功或失败的回调。

    结合这个思路,我们优化一下代码:

    class Promise {
      constructor (excutor) {
        // 规定状态
        this.state = 'peding'
        // 保存 `resolve(res)` 的res值
        this.value = undefined
        // 保存 `reject(err)` 的err值
        this.reason = undefined
        // 存放成功的回调
        this.successCB = []
        // 依次将对应的函数执行
        this.faildCB = []
    
        // 调用此方法就是成功
        let resolve = (value) => {
          // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
          if (this.state === 'peding') {
            this.state = 'fulfilled'
            this.value = value
            this.successCB.forEach(f => f())
          }
        }
    
        // 调用此方法就是失败
        let reject = (reason) => {
          // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
          if (this.state === 'peding') {
            this.state = 'rejected'
            this.reason = reason
            this.faildCB.forEach(f => f())
          }
        }
    
        try {
          // 立即执行,将 resolve 和 reject 函数传给使用者  
          excutor(resolve, reject)
        } catch (error) {
          // 若出错,直接调用reject
          reject(error)
        }
      }
    
      // 包含一个 then 方法,并接收两个参数 onFulfilled、onRejected
      then (onFulfilled, onRejected) {
        if (this.state === 'fulfilled') {
          onFulfilled(this.value)
        }
    
        if (this.state === 'rejected') {
          onRejected(this.reason)
        }
    
        if (this.state === 'peding') {
          this.successCB.push(() => { onFulfilled(this.value) })
          this.faildCB.push(() => { onRejected(this.reason) })
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58

    测试一下:

    let p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('ok2')
      }, 1000);
    }).then(res => {
      console.log(res);
    })
    
    1
    2
    3
    4
    5
    6
    7

    控制台等待 1s 后输出:

    ok2
    
    1

    ok!大功告成,异步问题已经解决了!

    熟悉设计模式的同学,应该意识到了这其实是一个发布订阅模式,这种收集依赖 -> 触发通知 -> 取出依赖执行的方式,被广泛运用于发布订阅模式的实现。


    # Promise.all

    promise.all 是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)。

    Promise.all = function (promises) {
      let list = []
      let count = 0
      function handle(i, data, resolve) {
        list[i] = data
        count++
        if (count == promises.length) {
          resolve(list)
        }
      }
      return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
          promises[i].then(res => {
            handle(i, res, resolve)
          }, err => reject(err))
        }
      })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    测试一下:

    let p1 = new Promise((resolve, reject) => {
        resolve('ok1');
    })
    
    let p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('ok2');
      }, 1000);
    })
    
    let p3 = new Promise((resolve, reject) => {
      resolve('ok3')
    })
    
    Promise.all([p1,p2,p3]).then(data => {
      console.log(data);
    }, err => {
      console.log('reject', err);
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    控制台等待 1s 后输出:

    ['ok1', 'ok2', 'ok3']
    
    1

    测试一下返回失败:

    let p1 = new Promise((resolve, reject) => {
        resolve('ok1');
    })
    
    let p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('ok2');
      }, 1000);
    })
    
    let p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('err');
      }, 1000);
    })
    
    Promise.all([p1,p2,p3]).then(data => {
      console.log(data);
    }, err => {
      console.log('reject:', err);
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    控制台等待 1s 后输出:

    reject: err
    
    1

    # Promise.race

    Promise.race 用来处理多个请求,采用最快的(谁先完成用谁的)。

    Promise.race = function (promises) {
      return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
          promises[i].then(res =>  {
            resolve(res)
          }, err => {
            reject(err)
          })
        }
      })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    测试一下:

    let p1 = new Promise((resolve, reject) => {
        resolve('ok1');
    })
    
    let p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('ok2');
      }, 1000);
    })
    
    let p3 = new Promise((resolve, reject) => {
        resolve('ok3');
    })
    
    Promise.race([p1,p2,p3]).then(data => {
      console.log(data);
    }, err => {
      console.log('reject', err);
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    控制台输出:

    ok1
    
    1

    此文只实现简单的Promise,完整的请移步前面发的链接去查看,最后祝大家生活愉快,谢谢!!

    @zwx

    上次更新: 10/17/2023, 3:17:45 PM
    编译原理期末复习公式
    Vue3 设置动态路由出现Error Unknown variable dynamic import

    ← 编译原理期末复习公式 Vue3 设置动态路由出现Error Unknown variable dynamic import→

    最近更新
    01
    在 M1 芯片 Mac 上使用 Homebrew
    11-08
    02
    GIT 遇到问题
    11-06
    03
    react Router嵌套路由不生效
    10-27
    更多文章>
    Theme by Vdoing | Copyright © 2019-2023 Evan Xu | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式
    ×