手写Promise过程记录

参考资料

Promises/A+规范中文

BAT前端经典面试问题:史上最最最详细的手写Promise教程(很详细,一步一步来,还有catch和resolve、reject、race、all方法,末尾有验证手写Promise正确性的代码)

手摸手教你实现Promise/A+规范(视频教程)

实现的promise文件

准备三个文件

  • index.js:原生的Promise
  • promise.js:手写Promise
  • test.js:测试promise.js是否正确

1.声明Promise

  • promise是一个类,所以使用class声明

  • 构造器constructor接收函数参数executor

  • 开始前先进行,参数校验,输入参数不是函数时,抛出TypeError错误

  • executor中有两个参数,分别是resolvereject,分别定义后,再传递给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函数立即执行了

image-20200915175624237

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种情形。

  1. x 与 promise2 相等,循环调用
  2. x为Promise,取它的结果作为新的promise2成功的结果。x的值为promise时,必须等待该promise执行结束,再进行。
  3. x为对象或函数
  4. 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)
    })
}
posted @ 2020-09-15 20:10  wattmelon  阅读(98)  评论(0编辑  收藏  举报