网上说 Promise
的文章一大堆,但是我还是要写一点作为自己的总结,看的东西不要多,主要是看怎么去实现了解其原理过程那就可以了,太复杂的说实话现在还没遇到碰到,就用这篇做一下记录笔记,日后翻看。
了解 Promise
之前先来看一下下面的问题。
什么是宏任务和微任务?
我们都知道 JavaScript
程序是单线程的,由上往下进行,如果碰到一些加载过慢导致延迟堵塞的情况下,就会考虑做成异步操作,比如说使用最多的 setTimeout
函数,为了不在同一时间执行的代码过多导致堵塞,把需要执行的代码延迟执行,已解决效率提升速度。有了异步当然还少不了同步,这两个模式也分别称为同步模式(Synchronous)和异步模式(Asynchronous)。
事实上异步的模式下还有两个任务,就是上面问 ”宏任务和微任务“ 。在 ES6 的规范中,宏任务(Macrotask)称为 Task,微任务(Microtask) 称为 Jobs。宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。
下面的简单比较?
宏任务 | 微任务 |
---|---|
setTimeout | requestAnimationFrame |
setInterval | Promise |
JavaScript代码块 | MutationObserver |
什么是 EventLoop?
这应该是大部分面试都会被问到的问题了,用图片看就清晰了。看下图;
JavaScript
运行浏览器异步模块时会去检查
是否有宏任务队列,如果有,就执行最早进入宏队列的任务,往下
是否有微任务队列,如果有,就执行最早进入微队列的任务,接着继续 继续检查微任务队列空不空,如果没有,就往下一步
因为首次加载代码块是执行的宏任务,所以在执行完 JavaScript
代码后,立即执行微任务,等所有的微任务走完之后,就是重新渲染。看下面的题目:
console.log('0');
setTimeout(function() {
console.log('4');
}, 0);
Promise.resolve().then(function() {
console.log('2');
}).then(function() {
console.log('3');
});
console.log('1');
结合上面所说的执行想一下是答案
返回结果是 0,1,2,3,4
解析:按上面所说的,首先应该进入 JS 执行的主线程(宏任务),按顺序执行,遇到异步跳过,然后再加载异步的微任务(Promise
),最后再去找宏任务的 setTimeout()
执行。
大致就是 代码块(宏)->同步->微任务->刷新->setTimeout
(宏)
大致说了 EventLoop
的简单原理,下面就要开始手写 Promise
了
手写Promise
开始写之前先了解 Promise
原有的几个特点
- 创建时需要传递一个立即执行的函数。
- 设置传入函数的两个回调(失败
reject
或者成功resolve
)。 Promise
有三个状态,Pending
等待,Fulfilled
完成,Rejected
失败。- ...
暂时做的简单版介绍
新建一个 MyPromise 类
传入一个立即执行的函数,执行函数设置两个回调函数,一个是失败 reject
一个是成功 resolve
class MyPromise {
constructor(handle) {
// 设置开始状态
this.status = 'pending'
// 绑定一下 this 指向
handle(this.resolve.bind(this), this.reject.bind(this))
}
resolve() {}
reject() {}
}
改变状态
优化一下代码,开始或者成功是每个状态是不一样的,所以成功或者失败都伴随一个状态的改变。在这里可以定义三个状态,Pending
等待,Fulfilled
完成,Rejected
失败。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(handle) {
// 设置开始状态
this.status = PENDING
// 成功之后返回的信息
this.value = null
// 失败之后返回的信息
this.error = null
// 绑定一下 this 指向
handle(this._resolve.bind(this), this._reject.bind(this))
}
_resolve(value) {
// 只有 'pending' 的状态时,才执行状态修改
if(this.status === PENDING) {
// 修改状态为 成功
this.status = FULFILLED
// 再保存成功之后的 值
this.value = value
}
}
_reject(error) {
// 只有 'pending' 的状态时,才执行状态修改
if(this.status === PENDING) {
// 修改状态为 失败
this.status = REJECTED
// 再保存失败之后的 值
this.error = error
}
}
}
then 的简单实现
上面的代码可以简单的介绍了 Promise
的初始化阶段和准备,下面还要加上内置的 then
方法使用才算完整。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(handle) {
// 设置开始状态
this.status = PENDING
// 成功之后返回的信息
this.value = null
// 失败之后返回的信息
this.error = null
// 绑定一下 this 指向
handle(this._resolve.bind(this), this._reject.bind(this))
}
then(resolve, reject) {
// 只有完成的状态才能使用 then
if (this.status === FULFILLED) {
resolve(this.value)
} else if (this.status === REJECTED) {
reject(this.error)
}
}
// 私有方法
_resolve(value) {
// 只有 'pending' 的状态时,才执行状态修改
if(this.status === PENDING) {
// 修改状态为 成功
this.status = FULFILLED
// 再保存成功之后的 值
this.value = value
}
}
_reject(error) {
// 只有 'pending' 的状态时,才执行状态修改
if(this.status === PENDING) {
// 修改状态为 失败
this.status = REJECTED
// 再保存失败之后的 值
this.error = error
}
}
}
测试代码
完成上述代码之后,浏览器测试一下预期效果:
// 引入 MyPromise.js
const p = new MyPromise((resolve, reject) => {
resolve('success')
reject('reject')
})
p.then((res) => {
console.log(res)
})
// 执行结果: success
很好,完美的实现了最简单的 Promise
给 Promise 加入异步的逻辑处理
上面的代码中只是实现了简单同步执行回调,并没有接入异步的处理程序,想要异步获取数据用 Promise 包裹起来,然后再使用。比如说下面加入 setTimeout
之后的代码,想一下会是什么样的效果;
// 引入 MyPromise.js
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000)
})
p.then((res) => {
console.log(res)
})
// 没有打印信息
为什么会出现没有打印信息的情况?分析一下原因:
当主线程代码进行时,遇到 setTimeout
异步函数,会进入异步队列中,但是 then
会马上执行,这时候异步队列中 resolve()
没有准备完成或者说没有执行完,所以现在的执行状态还是 'pending'
,因为没有在 resolve
设置 this.status = FULFILLED
,然而 then
函数中执行 pending
的时候没有判断,这时候就导致没有打印的信息出来。
下面就来修改一下之前的代码,把 then
函数中的 pending
也加上。
缓存成功和失败的回调
constructor(handle) {
// 设置开始状态
this.status = PENDING
// 成功之后返回的信息
this.value = null
// 失败之后返回的信息
this.error = null
/** 新增 **/
// 存储成功回调函数
this.onResolveCallback = null
// 存储失败回调函数
this.onRejectCallback = null
// 绑定一下 this 指向
handle(this._resolve.bind(this), this._reject.bind(this))
}
then 方法中加入 pending 的处理
then(resolve, reject) {
// 只有完成的状态才能使用 then
if (this.status === FULFILLED) {
resolve(this.value)
} else if (this.status === REJECTED) {
reject(this.error)
} else if (this.status === PENDING) {
// 把 pending 的状态下的成功和失败的回调保存起来
this.onResolveCallback = resolve
this.onRejectCallback = reject
}
}
在 resolve 与 reject 中调用保存的回调
_resolve(value) {
// 只有 'pending' 的状态时,才执行状态修改
if(this.status === PENDING) {
// 修改状态为 成功
this.status = FULFILLED
// 再保存成功之后的 值
this.value = value
// 判断是否有成功的这个函数
this.onResolveCallback && this.onResolveCallback(value)
}
}
_reject(error) {
// 只有 'pending' 的状态时,才执行状态修改
if(this.status === PENDING) {
// 修改状态为 失败
this.status = REJECTED
// 再保存失败之后的 值
this.error = error
// 判断是否有失败的这个函数
this.onRejectCallback && this.onRejectCallback(error)
}
}
这样一优化补充了代码之后,再去执行之前的代码,就会发现等 2s 之后会打印出 ”success“。
但是就上面的代码实现,还不能满足对原生 Promise 的实现,还有 then 方法的链式调用和 all 方法等等,具体可以看下一篇思路代码。
本文参考以下下文章:
从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节