参考资料
BAT前端经典面试问题:史上最最最详细的手写Promise教程(很详细,一步一步来,还有catch和resolve、reject、race、all方法,末尾有验证手写Promise正确性的代码)
手摸手教你实现Promise/A+规范(视频教程)
准备三个文件
- index.js:原生的Promise
- promise.js:手写Promise
- test.js:测试promise.js是否正确
1.声明Promise
-
promise是一个类,所以使用class声明
-
构造器
constructor
接收函数参数executor
-
开始前先进行,参数校验,输入参数不是函数时,抛出
TypeError
错误 -
executor
中有两个参数,分别是resolve
和reject
,分别定义后,再传递给executor
class Promise {
// 构造器接收函数参数executor
constructor(executor) {
// 参数校验
if (typeof executor !== 'function') {
throw new TypeError(`Promise resolver ${executor} is not a function`)
}
const resolve = function(){}
const reject = function(){}
// 传递给executor并执行
executor(resolve, reject)
}
}
module.exports = Promise;
// [test.js]
const Promise = require('./promise.js')
new Promise((resolve, reject) => {
console.log('开始')
resolve(1)
})
2.resolve和reject
resolve
: promise 成功时进行的一系列操作,如状态的改变、成功回调的执行(即Promise/A+中的解决fulfill)
reject
:promise 失败时进行的一系列操作,如状态的改变、失败回调的执行
value
:终值(eventual value),promise 被解决时传递给解决回调的值。这个值被传递时,标志着等待状态的结束
reason
:拒因,promise 被拒绝时传递给拒绝回调的值
一次性特征:Promise必须由等待状态迁移至执行态/拒绝态,状态改变后不可再次改变。
需要定义值state
记录状态的改变。
- Pending状态不可逆,在resolve和reject中,只有状态为Pending,才能继续执行下去
class Promise {
constructor(executor) {
// 参数校验
// ......
// 初始化值
this.value = null // 终值
this.reason = null // 拒因
this.state = Promise.PENDING // 状态(不可逆)
// 定义resolve
const resolve = function(value){
// 成功后的一系列操作(状态改变,成功回调的执行)
if (this.state === Promise.PENDING) {
// 状态变化为成功
this.state = Promise.FULFILLED
// 值的变化
this.value = value
}
}
// 定义reject
const reject = function(reason){
// 失败后的一系列操作(状态改变,失败回调的执行)
if (this.state === Promise.PENDING) {
// 状态变化为拒绝
this.state = Promise.REJECTD
// 值的变化
this.reason = reason
}
}
// 传递给executor并执行
// ......
}
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTD = 'rejected'
// [test.js]
// TypeError:Cannot read property 'state' of undefined
// 调用resovle/reject时是匿名调用,此时this=undefined ----> 使用箭头函数
// 定义resolve
const resolve = value => {
// ......
}
// 定义reject
const reject = reason => {
// ......
}
优化:
- 初始化值单独提取出来initValue()
- resolve和reject提取作为类的方法
class Promise {
// 接收函数参数executor
constructor(executor) {
// 参数校验
// ......
this.initValue()
// 传递给executor并执行
executor(this.resolve, this.reject)
}
// 初始化值
initValue() {
this.value = null // 终值
this.reason = null // 拒因
this.state = Promise.PENDING // 状态(不可逆)
}
// 定义resolve
resolve(value) {
if (this.state === Promise.PENDING) {
this.state = Promise.FULFILLED
this.value = value
}
}
// 定义reject
reject(reason) {
if (this.state === Promise.PENDING) {
this.state = Promise.REJECTD
this.reason = reason
}
}
}
// [test.js]
// TypeError:Cannot read property 'state' of undefined
// 调用resovle/reject时是匿名调用,此时this=undefined ----> bind绑定
class Promise {
// 接收函数参数executor
constructor(executor) {
// 参数校验
// ......
this.initValue()
this.initBind()
// 传递给executor并执行
executor(this.resolve, this.reject)
}
// 绑定this
initBind() {
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
}
// ......
}
3.then
promise.then(onFulfilled, onRejected)
参数可选,如果不是函数,其必须被忽略------>参数判断,如果不是函数,并不是完全忽略。
// [index.js]
new Promise((resolve,reject)=>{
console.log('开始')
resolve(1)
})
.then()
.then(value=>{
console.log('value',value)
},reason=>{
console.log('reason',reason)
})
/*
then()不是函数,什么都不传。
输出仍为 开始 value 1 [忽略]
*/
// 定义then
then(onFulfilled, onRejected) {
// 参数校验 不是函数-->穿透效果(连续调用then)
if (typeof onFulfilled !== 'function') {
// 重新返回value
onFulfilled = function (value) {
return value
}
}
if (typeof onRejected !== 'function') {
// 重新抛出reason
onRejected = function (reason) {
throw reason
}
}
}
onFulfilled
:
状态为fulfilled时,传入this.value。
promise执行结束后必须被调用,结束前不可被调用。调用次数不能超过一次。
onRejected
:
状态为rejected时,传入this.reason。
promise被拒绝执行后必须被调用,被拒绝结束前不可被调用。调用次数不能超过一次。
// 定义then
then(onFulfilled, onRejected) {
// ......
if (this.state === Promise.FULFILLED) {
onFulfilled(this.value)
}
if (this.state === Promise.REJECTD) {
onRejected(this.reason)
}
}
4.异步任务执行顺序
// 执行栈->微任务->宏任务
// 当主栈第一阶段执行完,查看是否有微任务队列,先执行微任务中所有任务,再执行宏任务队列
console.log('1') // 执行主栈输出1
new Promise((resolve, reject) => {
console.log('2') // executor立即执行,主栈输出2
resolve(1) // 执行栈,then中的resolve和reject属于微任务
}).then(value => {
console.log('4') // 微任务输出4
console.log('value', value) // 微任务输出value 1
}, reason => {
console.log('reason', reason)
})
console.log('3') // 执行栈输出3
[index.js]输出
[test.js]输出,onFulfilled函数立即执行了
if (this.state === Promise.FULFILLED) {
// onFulfilled和onRejected不能同时被调用,通过setTimeout异步调用
setTimeout(() => {
onFulfilled(this.value)
})
}
if (this.state === Promise.REJECTD) {
setTimeout(() => {
onRejected(this.reason)
})
}
此时,异步任务执行顺序模拟成功了。
测试在promise中抛出异常
console.log('1')
new Promise((resolve, reject) => {
throw new Error('chucuole')
resolve(1)
}).then(value => {
console.log('4')
console.log('value', value)
}, reason => {
console.log('reason', reason)
})
console.log('3')
// [index.js]出错信息放在了reject内的reason为 reason Error:chucuole
// try...catch捕获错误,交给reject
try {
// 传递给executor并执行
executor(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
测试promise内为异步任务
// 当主栈第一阶段执行完,查看是否有微任务队列,先执行微任务中所有任务,再执行宏任务队列
console.log('1')// 执行主栈输出1
new Promise((resolve, reject) => {
setTimeout(()=>{ // 放入宏任务队列,EventLoop此时无微任务,执行宏任务
console.log('haha')
resolve(1)
})
}).then(value => {
console.log('4') // 微任务输出4
console.log('value', value)// 微任务输出value 1
}, reason => {
console.log('reason', reason)
})
console.log('3') // 执行主栈输出3
//[index.js] 1 3 haha 4 value 1
//[test.js] 1 3 haha
原因:setTimeout()没有执行,就直接执行了then。(还没懂)state的状态一直是pending,所以没有进入resolve和reject中。
解决:
- 添加两个数组onFulfilledCallbacks和onRejectedCallbacks
- 将成功resolve和失败reject存到各自数组
- 一旦resolve或reject就调用它们
// 初始化值
initValue() {
// ......
this.onFulfilledCallbacks = [] // 成功回调
this.onRejectedCallbacks = [] // 失败回调
}
// 定义resolve
resolve(value) {
if (this.state === Promise.PENDING) {
this.state = Promise.FULFILLED
this.value = value
// 一旦resolve执行,调用成功数组的函数
this.onFulfilledCallbacks.forEach(fn => fn(this.value))
}
}
// 定义reject
reject(reason) {
if (this.state === Promise.PENDING) {
this.state = Promise.REJECTD
this.reason = reason
// 一旦reject执行,调用失败数组的函数
this.onRejectedCallbacks.forEach(fn => fn(this.reason))
}
}
// 状态为pending时,把onFulfilled和onRejected传入数组
if (this.state === Promise.PENDING) {
this.onFulfilledCallbacks.push((value) => {
setTimeout(() => {
onFulfilled(this.value)
})
})
this.onRejectedCallbacks.push((reason) => {
setTimeout(() => {
onRejected(this.reason)
})
})
}
5.链式调用
为了实现链式调用,且改变后面then的值,必须通过新的实例。
默认在第一个then中返回一个promise,即在then中返回新的promise(promise2)。
- 将这个promise2返回的值传递给下一个then
- 如果返回一个普通的值,则将普通的值传递给下一个then
具体实现中需要对第一个then返回的值(x)进行判断,通过定义resolvePromise判断。
let promise2 = new Promise((resolve, reject) => {
})
return promise2
let promise2 = new Promise((resolve, reject) => {
if (this.state === Promise.FULFILLED) {
setTimeout(() => {
const x = onFulfilled(this.value)
Promise.resolvePromise(promise2, x, resolve, reject)
})
}
if (this.state === Promise.REJECTD) {
setTimeout(() => {
const x = onRejected(this.reason)
Promise.resolvePromise(promise2, x, resolve, reject)
})
}
if (this.state === Promise.PENDING) {
this.onFulfilledCallbacks.push((value) => {
setTimeout(() => {
const x = onFulfilled(this.value)
Promise.resolvePromise(promise2, x, resolve, reject)
})
})
this.onRejectedCallbacks.push((reason) => {
setTimeout(() => {
const x = onRejected(this.reason)
Promise.resolvePromise(promise2, x, resolve, reject)
})
})
}
})
return promise2
// 判断x的函数 (默认返回的promise,自己return的对象,promise2的resolve,promise2的reject)
Promise.resolvePromise = function (promise2, x, resolve, reject) {}
当onFulfilled和onRejected抛出异常时,promise2拒绝执行,并返回拒因
try...catch
捕获异常,catch中reject。
if (this.state === Promise.FULFILLED) {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
Promise.resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
// 其他类似
6.链式调用中判断x的resolvePromise
判断x时有以下4种情形。
- x 与 promise2 相等,循环调用
- x为Promise,取它的结果作为新的promise2成功的结果。x的值为promise时,必须等待该promise执行结束,再进行。
- x为对象或函数
- x为普通值,直接作为promise2成功的结果
- x===promise2
// [index.js] 循环调用,自己等待自己完成
let p1 = new Promise(resolve=>{
resolve(1)
})
let p2 = p1.then(()=>{
return p2
})
// TypeError:Chaining cycle detected for promise
Promise.resolvePromise = function (promise2, x, resolve, reject) {
// x 与 Promise 相等
if (promise2 === x) {
// 避免循环调用
reject(new TypeError('Chaining cycle detected for promise'))
}
}
- x为promise
if (x instanceof Promise) {
x.then(value => {
Promise.resolvePromise(promise2, value, resolve, reject)
}, reason => {
reject(reason)
})
}
- x为对象或函数
- 判断有没有then方法,有的就默认为promise
- 没有的就是普通的对象
// null是object,需要另外排除
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
// 如果then是函数,默认为promise
if (typeof x.then === 'function') {
x.then(value => {
Promise.resolvePromise(promise2, value, resolve, reject)
}, reason => {
reject(reason)
})
} else {
// 普通对象
resolve(x)
}
} catch (e) {
// 取then出错
reject(e)
}
}
成功和失败只能调用一个,定义called防止多次调用
// 定义变量,是否被调用过
let called = false
// null是object,需要另外排除
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
if (typeof x.then === 'function') {
x.then(value => {
if (called) return
called = true
Promise.resolvePromise(promise2, value, resolve, reject)
}, reason => {
if (called) return
called = true
reject(reason)
})
} else {
if (called) return
called = true
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
}
- x为普通值
else {
// 普通值
resolve(x)
}
7.测试
安装插件npm i promises-aplus-tests -g
,在自己的promise.js末尾添上以下代码。
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
测试:npx promises-aplus-tests promise.js
8.debug
Otherwise, if x is an object or function,Let then be x.then
- 使用then代替x.then,使用call修改this指向
const then = x.then
if (typeof then === 'function') {
then.call(x, (value) => {
if (called) return
called = true
Promise.resolvePromise(promise2, value, resolve, reject)
}, reason => {
if (called) return
called = true
reject(reason)
})
}