通俗易懂的实现符合promise A+ 规范的自定义promise

531 阅读8分钟

目标:

实现一个promise A+规范的自定义promise

为什么出现promise 以及 promise规范的意义

promise 是js异步编程的一种解决方案,已经称为了es6的语法标准。es6提供了原生的promise对象。

promise A+ 是对这种异步操作的一种约束和规定。

英文版: promise A+规范英文
翻译版: promise A+规范 翻译版

大致翻阅下上面的文档,发现大概要实现这几种要求:

  1. 一个promise状态必须是等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)三种状态的一种

    ​ 1.1 当状态确定便不可以更改

    ​ 1.2 执行态必须拥有一个不可变的终值和拒绝态必须拥有一个不可变的据因

  2. 可以同步resolve(reject)、异步resolve(reject)

    2.1 多次执行异步resolve(reject)的时候也可以执行多次,而不是一次

  3. then 函数可以链式调用

    3.1 每次都需要返回一个新的promise对象,而不是通过调用this来进行链式调用

  4. 上一个then, return的值作为下一个回调的参数

    4.1 如果return的不是promise类型的数据,则作为下一个回调的参数,如果是promise类型的数据,则使用promise resolve出的数据

  5. then方法识别return 自己然后又继续调用.then,需要抛出异常。

  6. 实现构造器和resolve/reject时的异常捕获

  7. 将then方法变为可选参数, 可传可不传

7.1 将then方法变为也可传非函数类型的

7.2 promise A+ 规范规定,如果then方法中的两个回调不是函数,则必须被忽略。

  1. 实现.catch函数

  2. 实现finally函数

  3. 实现resolve静态方法, 如果传递的是promise对象,则直接返回,否则包装为一个promise然后resolve返回。

  4. 实现reject静态方法

  5. 实现all静态方法

  6. 实现race静态方法

MyPromise的代码实现

任务一

确定promise 类的基本模型,resolve成功有成功的终值,reject失败有失败的原因,而且状态确定便不可以进行更改了。

任务二

对MyPromise进行多次then的时候,同步的情况是成立,而异步的情况会被覆盖,因为then是立即执行的,但是resolve、reject并不一定是立即执行的。所以当遇到resolve/reject的pending状态时,将回调存到数组中,当异步完成,调用resolve/reject时,依次执行数组中的回调。

任务三

then方法进行链式调用,需要返回一个新的promise对象,有个小细节就是,then方法返回的promise是pending状态的,所以需要进行resolve/reject操作执行。

任务四

将回调返回数据注入到下一个then的resolve/reject的参数, 而push的时候push函数过去,在函数中执行回调。

测试代码

let promise = new MyPromise((resovle, reject) => {
    resovle('成功')
})
promise.then(data => {
    console.log(data)
    return 't1'
})
    .then(data => {
    console.log(data)
    return new MyPromise(resovle => resovle('sss'))
})
    .then(data => {
    console.log(data)
})
任务五

如果then方法回调返回的是自己,那么需要抛出异常。不需要动逻辑,只需要处理一下resolvePromise这个方法,加入一个形参代表了当前的promise对象,但是执行resolvePromise方法的时候,当前的promise对象还没有构造完成,所以使用一个小技巧,将执行这个resolvePromise的时候变为异步执行。那么就可以获取到当前对象了。

测试代码

let p1 = new MyPromise((resolve, reject) => {
    resolve('成功')
})
let p2 = p1.then(data => {
    console.log(data)
    return p2
})
p2.then(data => {
    console.log(data)
})
任务六

进行异常捕获

简单的异常捕获

任务七

对then方法传递的参数进行处理,非函数则忽略

then (successCallback, errorCallback) {
        // 将then方法变为可选参数
        // 原生的then方法如果传递的是不是函数,则自动解析为 value=>value
        successCallback = typeof successCallback === 'function' ? successCallback : value => value
        errorCallback = typeof errorCallback === 'function' ? errorCallback : err => { throw err }
        // .....
}
任务八

catch方法,对整个promise链条的异常进行捕获

语法糖

catch (failCallback) {
    return this.then(undefined, failCallback)
}
任务九

finally方法, 不管什么状态都会走finall方法,而且会返回promise对象可以继续调用.

finally方法需要保存上一任的值,然后传递到下一任

 finally (callback) {
     return this.then(value => {
         return MyPromise.resolve(callback()).then(() => value)
     }, reason => {
         return MyPromise.resolve(callback()).then(() => { throw reason })
     })
 }

  • 执行完以上九步,能使用一些promsie的基本功能了,现在添加一些静态方法
    • resolve、reject、all、race
任务十
// 实现resolve函数
static resolve (value) {
    if (value instanceof MyPromise) return value
    return new MyPromise(resolve => {
        resolve(value)
    })
}
任务十一
// 实现reject 
static reject (value) {
    if (value instanceof MyPromisereturn value
    return new MyPromise((resolve, reject) => {
        reject(value)
    })
}
任务十二
// 实现all
static all (array) {
    // 定义数组保存返回的promise结果
    return new MyPromise((resovle, reject) => {
        let index = 0
        let result = []
        for (let i = 0; i < array.length; i++) {
            let r = array[i]
            if (r instanceof MyPromise) {
                // 将其resolve的结果放到数组中
                r.then(data => addData(i, data), err => reject(err))
            } else {
                addData(i, r)
            }
        }
        // 传入的数据的index与其对应的返回结果的index要一一对应
        function addData (i, value) {
            result[i] = value
            index++
            // 因为是异步的,所以不能直接在外面resolve, 而是判断一下长度,如果解析完了,就reolve
            // ps: 注意: 这里是利用了let劫持块级作用域,虽然for循环是立即执行完毕的,但是因为let的原因,他的作用域保存的index还是执行时的index
            if (index === array.length) {
                resovle(result)
            }
        }
    })
}
任务十三
// 实现race
static race (array) {
    let flag = true
    return new MyPromise((resovle, reject) => {
        for (let i = 0; i < array.length && flag; i++) {
            let r = array[i]
            if (r instanceof MyPromise) {
                r.then(data => addData(data), err => reject(err))
            } else {
                addData(r)
            }
        }
        function addData (value) {
            resovle(value)
            flag = false
        }
    })
}
总结

promise是比较重要的es6的标准,手写promise,更能帮助理解promise的实现,在使用的过程中更能灵活运用。promise不仅仅是解决了回调地狱的问题,更是制定了js异步编程的标准。