手写Promise

245 阅读4分钟

前置知识:什么是Promise

实现Promise

  1. 确定需求:实现Promise
  2. 确定要用到的工具和依赖:
mocha(测试框架)
chai(断言库)
sinon(用于模拟一些函数)
ts-node(运行ts文件)
typescript(支持ts)
yarn add mocha chai sinon sinon-chai ts-node typescript -D
yarn add @types/mocha @types/chai @types/sinon @types/sinon-chai -D
mocha -r ts-node/register test/index.ts(可以让mocha用ts-node运行)

测试用例 (可跳过直接看源码)

满足这些条件,代码也就写完了

  1. Promise是一个类
  2. Promise()如果接受的不是一个函数就报错
  3. new Promise()返回的对象是有then方法的
  4. new Promise(fn)中的fn是立即执行的
  5. new Promise(fn) 中的fn接受两个函数
  6. promise.then(success)中的success 会在resolve被调用时执行
  7. promise.then(null,failed)中的failed会在reject被调用时执行
  8. promise.then(false,null) then中接受的不是函数会忽略,不报错
  9. promise(resolve,reject)中的resolve,reject只会被调用一次
  10. resolve拿到的结果与then成功的回调中的结果一致,reject拿到的结果与then中失败回调中的一致
  11. 在代码执行完之前不得执行then中的回调
  12. resolve,reject如果不穿this,默认为undefined
  13. then可以被调用多次,并且按顺序调用
  14. 返回一个promise
import * as chai from 'chai'
import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai'
import Promise2 from '../src'

chai.use(sinonChai)
const assert = chai.assert

describe('Promise', () => {
    it('它是一个类', () => {
        assert.isFunction(Promise2)
        assert.isObject(Promise2.prototype)
    })
    it('Promise()如果接受的不是一个函数就报错', () => {
        assert.throw(() => {
            //@ts-ignore
            new Promise2()//这里期待报错,不穿参会报错哦
            // new Promise2(() => { })//如果你写成这样test就过不了
        })
        assert.throw(() => {
            //@ts-ignore
            new Promise2(1)
        })
    })
    it('newPromise()返回的对象是有then方法的', () => {
        const promise = new Promise2(() => { })
        assert.isObject(promise)
        assert.isFunction(promise.then)
    })
    it('newPromise(fn)接受的fn是立即执行的', () => {
        const fn = sinon.fake()
        new Promise2(fn)
        assert(fn.called)

    })
    it('new Promise2(fn)fn接受两个函数', () => {
        new Promise2((resolve, reject) => {
            assert.isFunction(resolve)
            assert.isFunction(reject)
        })
    })
    it('promise.then(success,reject)中的success会在resolve调用时执行', () => {
        const succeed = sinon.fake()
        const promise = new Promise2((resolve, reject) => {
            assert(succeed.notCalled)
            resolve()
            setTimeout(() => {
                assert(succeed.called)
            })
        })
        promise.then(succeed)
    })
    it('promise.then(null,failed)中的failed会在reject调用时执行', (done) => {
        const failed = sinon.fake()
        const promise = new Promise2((resolve, reject) => {
            assert(failed.notCalled)
            reject()
            setTimeout(() => {
                assert(failed.called)
                done()
            })
        })
        promise.then(null, failed)
    })
    it('promise.then(false,null) then中接受的不是函数会忽略,不报错', () => {
        const promise = new Promise2((resolve, reject) => {
            resolve()
        })
        promise.then(false, null)
    })
    it('promise(resolve,reject)中的resolve,reject只会被调用一次', () => {
        const succeed = sinon.fake()
        const promise = new Promise2((resolve, reject) => {
            resolve()
            resolve()
            setTimeout(() => {
                assert(succeed.calledOnce)
            })
        })
        promise.then(succeed)
    })
    it('resolve拿到的结果与then成功的回调中的结果一致,reject拿到的结果与then中失败回调中的一致', () => {
        const promise = new Promise2((resolve, reject) => {
            resolve(123)
        })
        promise.then((res) => {
            assert(res === 123)
        })
    })
    it('在代码执行完之前不得执行then中的回调', (done) => {
        const succeed = sinon.fake()
        const promise = new Promise2((resolve, reject) => {
            resolve()
        })
        promise.then(succeed)
        assert(succeed.notCalled)
        setTimeout(() => {
            assert(succeed.called)
            done()
        })
        it('resolve,reject如果不穿this,默认为undefined', (done) => {
            const promise = new Promise2((resolve, reject) => {
                resolve()
            })
            promise.then(function () {
                'use strict'
                assert(this === undefined)
                done()
            })
        })

    })
    it('then可以被调用多次,并且按顺序调用', (done) => {
        const succeed = [sinon.fake(), sinon.fake(), sinon.fake()]
        const promise = new Promise2((resolve, reject) => {
            resolve()
        })
        promise.then(succeed[0])
        promise.then(succeed[1])
        promise.then(succeed[2])
        setTimeout(() => {
            assert(succeed[0].called)
            assert(succeed[1].called)
            assert(succeed[2].called)
            assert(succeed[1].calledAfter(succeed[0]))
            assert(succeed[2].calledAfter(succeed[1]))
            done()
        })

    })
    it('then返回一个Promise,可以连续then并且可以把结果往后传递', (done) => {
        const promise = new Promise2((resolve, reject) => {
            resolve()
        })
        const promise2 = promise.then(() => '成功').then(res => {
            assert.equal(res, '成功')
            done()
        })
        assert(promise2 instanceof Promise2)
    })
    it('then中返回的promise 可以结果成功会走resolve/失败可以走reject', (done) => {
        const fn = sinon.fake()
        const promise1 = new Promise2((resolve, reject) => {
            resolve()
        })
        const promise2 = promise1.then(() => new Promise2(resolve => resolve()))
        promise2.then(fn)
        setTimeout(() => {
            assert(fn.called)
            done()
        })

    })
})

promise代码

//nextTick可以换成setTimeout,这里为了写测试用例用了优先级更高的MutationObsever
class Promise2 {
    callbacks = []
    status = 'pending'
    resolveOrReject = (status, index, resultOrReason) => {
        if (this.status !== 'pending') return;
        this.status = status
        nextTick(() => {
            this.callbacks.forEach(handle => {
                if (typeof handle[index] === 'function') {
                    let x
                    try {
                        x = handle[index].call(undefined, resultOrReason)
                    } catch (e) {
                        return this.reject(e)
                    }
                    handle[2].resolveWith(x)
                }
            })
        })
    }
    resolve = (result) => {
        this.resolveOrReject('fulfilled', 0, result)
    }
    reject = (reason) => {
        this.resolveOrReject('rejected', 1, reason)
    }

    constructor(fn) {
        if (typeof fn !== 'function') {
            throw new Error('Promise只接受函数')
        }
        fn(this.resolve.bind(this), this.reject.bind(this))
    }
    then(success?, failed?) {
        const handle = [];
        (typeof success === 'function') && (handle[0] = success);
        (typeof failed === 'function') && (handle[1] = failed);
        handle[2] = new Promise2(() => { })
        this.callbacks.push(handle)
        return handle[2]
    }
    resolveWith(x) {
        if (this === x) {
            return this.reject(new TypeError())
        }
        if (x instanceof Promise2) {
            x.then((result) => {
                this.resolve(result)
            }, (reason) => {
                this.reject(reason)
            })
        }
        if (x instanceof Object) {
            let then
            try {
                then = x.then
            } catch (e) {
                this.reject(e)
            }
            if (then instanceof Function) {
                try {
                    x.then(y => {
                        y => {
                            this.resolveWith(y)
                        }
                    }, r => {
                        this.reject(r)
                    })
                } catch (e) {
                    this.reject(e)
                }

            } else {
                this.resolve(x)
            }
        } else {
            this.resolve(x)
        }
    }
}

export default Promise2

function nextTick(fn) {//这个函数用来解决setTimeout优先级的问题可以忽略
    if (process !== undefined && typeof process.nextTick === "function") {
        return process.nextTick(fn)
    } else {
        var counter = 1
        var observe = new MutationObserver(fn)
        var textNode = document.createTextNode(String(counter))
        observe.observe(textNode, {
            characterData: true
        })
        counter = (counter + 1) % 2
        textNode.data = String(counter)
    }
}

图解 图解

返回一个Primose

promise的状态改变

Promise 有三种状态:Pending 初始态; Fulfilled 成功态; Rejected 失败态。

  1. pending变成Fulfilled
  2. pending变成Rejected 说明:只有这两种,且一个promise对象只能改变一次,无论变为成功还是失败,都会有一个结果数据,成功的结果数据一般称为vlaue,失败的结果数据一般称为reason.

promise的缺点是什么? -cons

如何解决这些缺点? -more

MutationObserver(nextTick实现原理)

//在写测试用例事时因为setTimeout优先级问题,已经不能满足正常测试! 我们可以使用nextTick代替

function nextTick(fn) {
    if (process !== undefined && typeof process.nextTick === "function") {
        return process.nextTick(fn)
    } else {
        var counter = 1
        var observe = new MutationObserver(fn)
        var textNode = document.createTextNode(String(counter))
        observe.observe(textNode, {
            characterData: true
        })
        counter = (counter + 1) % 2
        textNode.data = String(counter)
    }
}