Promise手写实践

103 阅读7分钟

promise标准

promiseA+规范

Promise表示一个异步操作的最终结果。与Promise最主要的交互方法是通过将函数传入它的then方法从而获取得Promise最终的值或Promise最终拒绝(reject)的原因。

1. 术语

promise是一个包含了兼容promise规范then方法的对象或函数,
thenable 是一个包含了then方法的对象或函数。
value 是任何Javascript值。 (包括 undefined, thenable, promise等).
exception 是由throw表达式抛出来的值。
reason 是一个用于描述Promise被拒绝原因的值。

2. 要求

  1. Promise状态:
    • 一个Promise必须处在其中之一的状态:pending, fulfilled 或 rejected.
    • 如果是pending状态,则promise: 可以转换到fulfilled或rejected状态。
    • 如果是fulfilled状态,则promise:
      • 不能转换成任何其它状态。
      • 必须有一个值,且这个值不能被改变。
    • 如果是rejected状态,则promise:
      • 不能转换成任何其它状态。
      • 必须有一个原因,且这个值不能被改变。

3. then方法

一个Promise必须提供一个then方法来获取其值或原因。 Promise的then方法接受两个参数:

    promise.then(onFulfilled, onRejected)

参数可选 onFulfilled 和 onRejected 都是可选参数。

  • 如果 onFulfilled 不是函数,其必须被忽略
  • 如果 onRejected 不是函数,其必须被忽略

onFulfilled 特性 如果 onFulfilled 是函数:

  • 当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值
  • 在 promise 执行结束前其不可被调用
  • 其调用次数不可超过一次

onRejected 特性 如果 onRejected 是函数:

  • 当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因
  • 在 promise 被拒绝执行前其不可被调用
  • 其调用次数不可超过一次

多次调用 then 方法可以被同一个 promise 调用多次

  • 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
  • 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调

返回 then 方法必须返回一个 promise

    promise2 = promise1.then(onFulfilled, onRejected);

  • 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:Resolve(promise2, x)
  • 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
  • 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
  • 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因

4. Promise解决过程

Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。

这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。

运行 [[Resolve]](promise, x) 需遵循以下步骤:

x 与 promise 相等

  • 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise

x 为 Promise

  • 如果 x 为 Promise ,则使 promise 接受 x 的状态:
  • 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
  • 如果 x 处于执行态,用相同的值执行 promise
  • 如果 x 处于拒绝态,用相同的据因拒绝 promise

x 为对象或函数

如果 x 为对象或者函数:

  • 把 x.then 赋值给 then
  • 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
  • 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
    • 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
    • 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
    • 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
    • 如果调用 then 方法抛出了异常 e:
      • 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
      • 否则以 e 为据因拒绝 promise
    • 如果 then 不是函数,以 x 为参数执行 promise
  • 如果 x 不为对象或者函数,以 x 为参数执行 promise

5. 实现代码

    const PENDING = 'PENGDING'; // 默认等待状态
    const FULLFILLED = 'FULLFILLED'; // 成功态
    const REJECTED = 'REJECTED' // 失败态

    // 考虑 x 可能是外部的promise
    function resolvePromise(x, promise2, resolve, reject){
        // promiseA+ 规范 :If promise and x refer to the same object, reject promise with a TypeError as the reason
        if (x === promise2){
            return new TypeError('循环引用')
        }
        // 1. 如果x 是一个普通值 则直接调用resolve即可
        // 2. 如果x 是一个promise那么应该采用这个promise的状态 决定调用的是 resolve还是reject
        if(((typeof x === 'object' && x !== null)) || (typeof x == 'function')){
            // 是对象或者函数才有可能是个promise
            let called = false;
            try {
                // 取then方法 并且用try捕捉可能出现的错误 防止别人的promise中设置了取值的限制
                // 比如:Object.defineProperty(x,'then',{
                //     get(){
                //         if(times ==2){
                //             throw new Error()
                //         }
                //     }
                // })
                let then = x.then
                if (typeof then == 'function'){
                    // 用call不用x.then是为了少取一次then少触发get方法
                    then.call(x,y=>{ // y也可能是个prmosie 要递归处理
                        if (called) return;
                        called = true
                        // 不停的解析成功的promise中返回的成功值,直到这个值是一个普通值
                        resolvePromise(y, promise2, resolve, reject);
                    },r=>{
                        if (called) return;
                        called = true
                        reject(r);
                    })
                }
            }catch(e){
                if (called) return;
                called = true
                reject(e); // 让promise2 变成失败态
            }
        }else {
            // x 是一个普通值
            resolve(x)
        }
    }
    class Promise{
        constructor(executor){
            this.status = PENDING;
            this.value = undefined; // 成功时的值
            this.reason = undefined;  // 失败时的值
            // 存在异步逻辑时先存放回调函数 ,异步完成时在resolve/reject再遍历其中的回调依次执行
            this.onResolvedCallbacks = [];
            this.onRejectedCallbacks = [];
            // 调用resolve和reject可以将对应的结果暴露在当前的promise实例上

            // 为什么resolve,reject不写在原型上 因为  每个promise有自己的resolve,reject
            const resolve = (value)=>{
                // 只有状态在PENDING时才能修改状态 保证只能调用一次resolve/reject,状态修改不可逆,
                if(value instanceof Promise){
                    return value.then(resolve,reject)
                }
                if (this.status === PENDING){
                    this.value = value;
                    this.status = FULLFILLED;
                    this.onResolvedCallbacks.forEach(fn=>fn())
                }
            }
            const reject = (reason) => {
                if (this.status === PENDING) {
                    this.reason = reason;
                    this.status = REJECTED;
                    this.onRejectedCallbacks.forEach(fn => fn())
                }
            }
            try {
                // 默认new Promise中的函数会立即执行
                executor(resolve, reject)
            }catch(e){
                // throw new Error或者执行出错时,需要将错误传递到reject中,执行失败的逻辑
                reject(e)
            }

        }

        // 在then方法(成功和失败)中 返回一个promise, promose会采用返回的promise的成功的值或失败原因, 传递到外层下一次then中

        // 1. then方法中 成功的回调或者失败的回调返回的是一个promise,那么会采用返回的promise的状态,走外层下一次then中的成功或失败, 同时将promise处理后的结果向下传递
        // 2.then方法中 成功的回调或者失败的回调返回的是一个普通值 (不是promise) 这里会将返回的结果传递到下一次then的成功中去
        // 3.如果在then方法中 成功的回调或者失败的回调 执行时出错会走到外层下一个then中的失败中去
        then(onFulfilled,onRejected){
            debugger
            // 可选参数 then中的onFulfilled,onRejected为空则将上个then中的value或者reason传递到下个then中
            onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v=>v;
            onRejected = typeof onRejected === 'function' ? onRejected : e => {throw(e)}
            console.log(onFulfilled)
            let promise2 = new Promise((resolve,reject)=>{
                if (this.status === FULLFILLED){
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value)
                            resolvePromise(x, promise2, resolve, reject)
                        }catch(e){
                            reject(e)
                        }
                    }, 0);
                }
                if (this.status === REJECTED) {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason)
                            resolvePromise(x, promise2, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    }, 0);
                }
                // 执行then方法状态是PENDING时 表示存在异步逻辑 使用发布订阅模式先将回调保存,异步结束调用resolve/reject再处理
                if (this.status === PENDING) {
                this.onResolvedCallbacks.push(()=>{
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value)
                            resolvePromise(x, promise2, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    }, 0);
                })
                    this.onRejectedCallbacks.push(() => {
                        setTimeout(() => {
                            try {
                                let x = onRejected(this.reason)
                                resolvePromise(x, promise2, resolve, reject)
                            } catch (e) {
                                reject(e)
                            }
                        }, 0);
                    })
                }
            })
            return promise2
        }
        catch(errFn){
            return this.then(null, errFn)
        }

        static resolve(value){
            return new Promise((resolve,reject)=>{
                resolve(value)
            })
        }

        static reject(reason){
            return new Promise((resolve,reject)=>{
                reject(reason)
            })
        }

        static all(promises){
            return new Promise((resolve,reject)=>{
                let result = []
                let index = 0
                function process(v, k) {
                    result[k] = v;
                    if (++index == promises.length) { // 解决多个异步并发问题 靠计数器
                        resolve(result);
                    }
                }
                for (let i = 0; i < promises.length; i++) {
                    let p = promises[i]
                    if(p && typeof p.then === 'function'){
                        p.then(data=>{
                            process(data, i);// 异步的
                        },reject)
                    }else {
                        process(p, i);// 同步的
                    }
                }
            })
        }
    }
    module.exports = Promise

6.promise 使用例子

    const Promise = require('./myPromise')

    let promise  = new Promise((resolve,reject)=>{
        setTimeout(() => {
            reject('第一个error')
            resolve('ok')
        }, 1000);
    })
    let promise2 = promise.then(value=>{
        console.log(value,'success')
        // return new Promise((r,e)=>{e('e')})
    },reason=>{
        console.log(reason,'fail')
        return new Promise((resolve,reject)=>{
            reject('新promise')
        })
    })
    promise2.then().then().then(value => {
        console.log(value, 'success2')
    }, reason => {
        console.log(reason, 'fail2')
    })

    promise2.then().then().then(value => {
        console.log(value, 'success2')
    }).catch(err => {
        console.log(err, 'err');
    })

    Promise.resolve(321312312).then(data=>{
        console.log('Promise.resolve',data)
    })

    Promise.resolve(new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('延时')
        }, 2000);
    })).then(data=>{
        console.log(data)
    })

    Promise.reject('错误').catch(err => {
        console.log('Promise.reject', err)
    })

    const fs = require('fs').promises

    // Promise.all 表示全部成功才成功, 如果一个失败了 则失败
    Promise.all([fs.readFile(__dirname + '/5.class.js', 'utf8'), fs.readFile(__dirname + '/4.reduce.js', 'utf8'), 11]).then(data => {
        console.log(data);
    }).catch(err => {
        console.log(err)
    })
    Promise.resolve(1).then(2).then(Promise.resolve(3)).then(x=>console.log(x))