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

    • HTML
    • CSS
    • JavaScript
    • Vue
    • 组件与插件
    • CSS扩展语言
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《Vue》笔记
    • 《TypeScript 从零实现 axios》
    • 《Git》学习笔记
    • TypeScript笔记
    • JS设计模式总结笔记
  • 前端框架面试题汇总
  • 基本面试题
  • 进阶面试题
  • 其它
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 前后端联调
  • mock.js
  • 奇技淫巧
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

Xin

英雄可不能临阵脱逃啊~
首页
  • 前端文章

    • HTML
    • CSS
    • JavaScript
    • Vue
    • 组件与插件
    • CSS扩展语言
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《Vue》笔记
    • 《TypeScript 从零实现 axios》
    • 《Git》学习笔记
    • TypeScript笔记
    • JS设计模式总结笔记
  • 前端框架面试题汇总
  • 基本面试题
  • 进阶面试题
  • 其它
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 前后端联调
  • mock.js
  • 奇技淫巧
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)
  • HTML

  • CSS

  • JavaScript

    • 知识点

      • new命令原理
      • ES5面向对象
      • ES6面向对象
      • 多种数组去重性能对比
      • 比typeof运算符更准确的类型判断
      • 可选链操作符
      • 三点运算符
      • cookies,sessionStorage和localStorage的区别?
      • forEach()函数究竟会不会修改原数组?
      • 宏任务与微任务
      • Promise实现原理
        • Promise 核心逻辑实现
          • 基本原理
          • 我的理解
        • 在 Promise 中加入异步逻辑
          • 缓存成功与失败回调
          • then 方法中的 Pending 的处理
          • resolve 与 reject 中调用回调函数
          • 我的理解
        • 实现 then 方法多次调用添加多个处理函数
          • MyPromise 类中新增两个数组
          • 回调函数存入数组中
          • 循环调用 成功和失败回调
        • 实现then方法的链式调用
      • this指向
      • 递归函数返回undefined
      • 使用JS的FileReader对象读取blob对象二进制数据
      • clipboard 剪切板属性
    • 方法

  • Vue

  • 组件与插件

  • css扩展语言

  • 学习笔记

  • 前端
  • JavaScript
  • 知识点
ctrlwin
2021-05-28

Promise实现原理

# Promise实现原理

我们都知道 Js 是单线程的,但是一些高耗时操作就带来了进程阻塞问题。为了解决这个问题,Js 有两种任务的执行模式:同步模式(Synchronous)和异步模式(Asynchronous)。

在异步模式下,创建异步任务主要分为宏任务与微任务两种。ES6 规范中,宏任务(Macrotask) 称为 Task, 微任务(Microtask) 称为 Jobs。宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。

原文链接:从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节 (opens new window)

# Promise 核心逻辑实现

原生🌰:

const promise = new Promise((resolve, reject) => {
   resolve('success')
   reject('err')
})

promise.then(value => {
  console.log('resolve', value)
}, reason => {
  console.log('reject', reason)
})

// 输出 resolve success
1
2
3
4
5
6
7
8
9
10
11
12

# 基本原理

Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行

Promise 会有三种状态

  • Pending 等待
  • Fulfilled 完成
  • Rejected 失败

状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;

Promise 中使用 resolve 和 reject 两个函数来更改状态;

then 方法内部做的事情就是状态判断

  • 如果状态是成功,调用成功回调函数
  • 如果状态是失败,调用失败回调函数

直接上代码:

// MyPromise.js

// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// 新建 MyPromise 类
export default class MyPromise {  // 使用export default向外暴露MyPromise类
  constructor(executor){
    // executor 是一个执行器,进入会立即执行
    // 并传入resolve和reject方法
    executor(this.resolve, this.reject)
  }

  // 储存状态的变量,初始值是 pending
  status = PENDING;

  // resolve和reject为什么要用箭头函数?
  // 如果直接调用的话,普通函数this指向的是window或者undefined
  // 用箭头函数就可以让this指向当前实例对象
  // 成功之后的值
  value = null;
  // 失败之后的原因
  reason = null;

  // 更改成功后的状态
  resolve = (value) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态修改为成功
      this.status = FULFILLED;
      // 保存成功之后的值
      this.value = value;
    }
  }

  // 更改失败后的状态
  reject = (reason) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态成功为失败
      this.status = REJECTED;
      // 保存失败后的原因
      this.reason = reason;
    }
  }

  then(onFulfilled, onRejected) {
    // 判断状态
    if (this.status === FULFILLED) {
      // 调用成功回调,并且把值返回
      onFulfilled(this.value);
    } else if (this.status === 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
48
49
50
51
52
53
54
55
56
57
58
59

# 我的理解

image

左图为promise 逻辑代码,右图为实例应用代码

通过executor传入resolve和reject方法,随后在实例方法中调用resolve方法。此时MyPromise.js中的resolve方法会执行,修改状态并保存传入的值。

// MyPromise.js
// 更改成功后的状态
resolve = (value) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
        // 状态修改为成功
        this.status = FULFILLED;
        // 保存成功之后的值
        this.value = value;
    }
}
1
2
3
4
5
6
7
8
9
10
11

随后会执行then方法,它会判断当前状态是成功还是失败,并返回值。随后在实例方法中接收并输出。

// MyPromise.js
then(onFulfilled, onRejected) {
        // 判断状态
        if (this.status === FULFILLED) {
            // 调用成功回调,并且把值返回
            onFulfilled(this.value);
        } else if (this.status === REJECTED) {
            // 调用失败回调,并且把原因返回
            onRejected(this.reason);
        } 
    }
1
2
3
4
5
6
7
8
9
10
11

onFulfilled和onRejected保存的是实例中的代码如:value{console.log(...)}

// 实例
promise.then(value => {
  console.log('resolve', value) // 输出resolve success
}, reason => {
  console.log('reject', reason)
})
1
2
3
4
5
6

# 在 Promise 中加入异步逻辑

先看🌰

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 2000); 
})

promise.then(value => {
  console.log('resolve', value)
}, reason => {
  console.log('reject', reason)
})

// 没有打印信息!!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

这是因为:

主线程代码立即执行,setTimeout 是异步代码,then 会马上执行,这个时候判断 Promise 状态,状态是 Pending,然而之前并没有判断等待这个状态

对代码进行改造:

# 缓存成功与失败回调

// MyPromise.js

// 在yPromise 类中新增
// 存储成功回调函数
onFulfilledCallback = null;
// 存储失败回调函数
onRejectedCallback = null;
1
2
3
4
5
6
7

# then 方法中的 Pending 的处理

// MyPromise.js

then(onFulfilled, onRejected) {
  // 判断状态
  if (this.status === FULFILLED) {
    // 调用成功回调,并且把值返回
    onFulfilled(this.value);
  } else if (this.status === REJECTED) {
    // 调用失败回调,并且把原因返回
    onRejected(this.reason);
  } else if (this.status === PENDING) {
    // ==== 新增 ====
    // 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
    // 等到执行成功失败函数的时候再传递
    this.onFulfilledCallback = onFulfilled;
    this.onRejectedCallback = onRejected;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# resolve 与 reject 中调用回调函数

// MyPromise.js

// 更改成功后的状态
resolve = (value) => {
  // 只有状态是等待,才执行状态修改
  if (this.status === PENDING) {
    // 状态修改为成功
    this.status = FULFILLED;
    // 保存成功之后的值
    this.value = value;
    // ==== 新增 ====
    // 判断成功回调是否存在,如果存在就调用
    this.onFulfilledCallback && this.onFulfilledCallback(value);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// MyPromise.js
// 更改失败后的状态
reject = (reason) => {
  // 只有状态是等待,才执行状态修改
  if (this.status === PENDING) {
    // 状态成功为失败
    this.status = REJECTED;
    // 保存失败后的原因
    this.reason = reason;
    // ==== 新增 ====
    // 判断失败回调是否存在,如果存在就调用
    this.onRejectedCallback && this.onRejectedCallback(reason)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

最后再执行一下🌰:

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 2000); 
})

promise.then(value => {
  console.log('resolve', value)
}, reason => {
  console.log('reject', reason)
})

// 等待 2s后 输出 resolve success
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 我的理解

在promise执行异步任务时,主线程代码会立即执行,而setTimeout是异步代码,所以会先执行.then中的代码,这时候MyPromise.js中的then会判断当前状态,如果是PENDING则将.then中的成功和失败回调保存并在异步任务执行完后,调用resolve或reject时,返回保存的成功或失败回调。

# 实现 then 方法多次调用添加多个处理函数

先看一个🌰:

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 2000); 
})

promise.then(value => {
  console.log(1)
  console.log('resolve', value)
})
 
promise.then(value => {
  console.log(2)
  console.log('resolve', value)
})

promise.then(value => {
  console.log(3)
  console.log('resolve', value)
})

// 3
// resolve success
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

目前的代码只打印出了第三个then的调用,Promise的then方法是可以被多次调用的。这里有三个then的调用,如果是同步回调,那么直接返回当前值;如果是异步回调,那么保存的成功或失败的回调需要使用不同的变量保存。

# MyPromise 类中新增两个数组

// MyPromise.js

// 存储成功回调函数
// onFulfilledCallback = null;
onFulfilledCallbacks = [];
// 存储失败回调函数
// onRejectedCallback = null;
onRejectedCallbacks = [];
1
2
3
4
5
6
7
8

# 回调函数存入数组中

// MyPromise.js

then(onFulfilled, onRejected) {
  // 判断状态
  if (this.status === FULFILLED) {
    // 调用成功回调,并且把值返回
    onFulfilled(this.value);
  } else if (this.status === REJECTED) {
    // 调用失败回调,并且把原因返回
    onRejected(this.reason);
  } else if (this.status === PENDING) {
    // ==== 新增 ====
    // 因为不知道后面状态的变化,这里先将成功回调和失败回调存储起来
    // 等待后续调用
    this.onFulfilledCallbacks.push(onFulfilled);
    this.onRejectedCallbacks.push(onRejected);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 循环调用 成功和失败回调

// MyPromise.js

// 更改成功后的状态
resolve = (value) => {
  // 只有状态是等待,才执行状态修改
  if (this.status === PENDING) {
    // 状态修改为成功
    this.status = FULFILLED;
    // 保存成功之后的值
    this.value = value;
    // ==== 新增 ====
    // resolve里面将所有成功的回调拿出来执行
    while (this.onFulfilledCallbacks.length) {
      // Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
      this.onFulfilledCallbacks.shift()(value)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// MyPromise.js

// 更改失败后的状态
reject = (reason) => {
  // 只有状态是等待,才执行状态修改
  if (this.status === PENDING) {
    // 状态成功为失败
    this.status = REJECTED;
    // 保存失败后的原因
    this.reason = reason;
    // ==== 新增 ====
    // resolve里面将所有失败的回调拿出来执行
    while (this.onRejectedCallbacks.length) {
      this.onRejectedCallbacks.shift()(reason)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

再来执行一下🌰:

// 完美打印所有then的调用
1
resolve success
2
resolve success
3
resolve success
1
2
3
4
5
6
7

# 实现then方法的链式调用

then 方法要链式调用那么就需要返回一个 Promise 对象 then 方法里面 return 一个返回值作为下一个 then 方法的参数,如果是 return 一个 Promise 对象,那么就需要判断它的状态

举个🌰:

// test.js

const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
  // 目前这里只处理同步的问题
  resolve('success')
})

function other () {
  return new MyPromise((resolve, reject) =>{
    resolve('other')
  })
}
promise.then(value => {
  console.log(1)
  console.log('resolve', value)
  return other()
}).then(value => {
  console.log(2)
  console.log('resolve', value)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

用目前的手写代码会报错,无法链式调用

修改一下代码:

// MyPromise.js

class MyPromise {
  ......
  then(onFulfilled, onRejected) {
    // ==== 新增 ====
    // 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
    const promise2 = new MyPromise((resolve, reject) => {
      // 这里的内容在执行器中,会立即执行
      if (this.status === FULFILLED) {
        // 获取成功回调函数的执行结果
        const x = onFulfilled(this.value);
        // 传入 resolvePromise 集中处理
        resolvePromise(x, resolve, reject);
      } else if (this.status === REJECTED) {
        onRejected(this.reason);
      } else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled);
        this.onRejectedCallbacks.push(onRejected);
      }
    }) 
    
    return promise2;
  }
}

function resolvePromise(x, resolve, reject) {
  // 判断x是不是 MyPromise 实例对象
  if(x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    // x.then(value => resolve(value), reason => reject(reason))
    // 简化之后
    x.then(resolve, reject)
  } else{
    // 普通值
    resolve(x)
  }
}
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

执行后的结果:

1
resolve success
2
resolve other
1
2
3
4
在GitHub上编辑 (opens new window)
#js
上次更新: 2/23/2022, 5:36:03 PM

← 宏任务与微任务 this指向→

最近更新
01
createElement函数创建虚拟DOM
05-26
02
clipboard 剪切板属性
05-26
03
vue的权限管理
05-16
更多文章>
Theme by Vdoing | Copyright © 2021-2022 Xin | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×