JavaScript 之 Promise

932 阅读6分钟

Promise

Promise 是异步编程的一种解决方案。

Promise 有三个状态:pending(等待态)、fulfilled(成功态)、rejected(失败态),状态只能从 pending->fulfilled/rejected。状态一旦改变就不会再变化,状态一旦改变。

创造 Promise 实例后,它会立即执行。

Promise 解决的问题

Promise是用来解决两个问题:

  1. 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象;
  2. Promise 可以支持多个并发的请求,获取并发请求中的数据,这个 Promise 可以解决异步的问题,本身不能说 Promise 是异步的。

API

Promise 是一个构造函数,本身有 resolverejectall 方法,原型上有 thencatch 等方法。

构造函数

var p = new Promise((resolve, reject) => {})
  • resolve :异步操作执行成功后的回调函数;
  • reject:异步操作执行失败后的回调函数。

then 链式调用

p.then(resolveFn, rejectFn)

then方法可以接受两个参数,第一个对应resolve的回调函数,第二个对应reject的回调函数。

all

Promiseall 方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。

all 接收一个数组参数,里面的值最终算返回 Promise 对象。

Promise.all([promise1, promise2, ...])

实现 Promise

基础版

  1. 可以创建promise对象实例;
  2. promise实例传入的异步方法执行成功就执行注册的成功回调函数,失败就执行注册的失败回调函数。
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise {
    constructor (fn) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined

        this.onFullFilledFn = null
        this.onRejectedFn = null

        let resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULFILLED
                this.value = value
                this.onFullFilledFn(this.value)
            }
        }
        let reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED
                this.reason = reason
                this.onRejectedFn(this.reason)
            }
        }
        try {
            fn(resolve, reject)
        } catch (err) {
            reject(err)
        }
    }
}
MyPromise.prototype.then = function (onFullFilled, onRejected) {
    this.onFullFilledFn = onFullFilled
    this.onRejectedFn = onRejected
}

支持同步任务

let promise = new MyPromise((resolve, reject) => {
    resolve("同步任务执行")
})
promise.then((val) => {console.log(val)})

执行上面的代码,此时会报错: "this.onFullFilledFn is not a function",这是因为我们的Promise实例是个同步任务,它的then方法还没被调用,所以回调函数还没有被注册,此时调用必然会报错。

解决方案:在reslove和reject里面用setTimeout进行包裹,使其到then方法执行之后再去执行

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise {
    constructor (fn) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined

        this.onFullFilledFn = null
        this.onRejectedFn = null

        let resolve = (value) => {
            if (this.status === PENDING) {
                setTimeout(() => {
                    this.status = FULFILLED
                    this.value = value
                    this.onFullFilledFn(this.value)
                })
            }
        }
        let reject = (reason) => {
            if (this.status === PENDING) {
                setTimeout(() => {
                    this.status = REJECTED
                    this.reason = reason
                    this.onRejectedFn(this.reason)
                })
            }
        }
        try {
            fn(resolve, reject)
        } catch (err) {
            reject(err)
        }
    }
}

Promise 三种状态

MyPromise.prototype.then = function (onFullFilled, onRejected) {
    // 如果状态是fulfilled,直接执行成功回调,并将成功值传入
    if (this.status === FULFILLED) {
        onFullFilled(this.value)
    }
    // 如果状态是rejected,直接执行失败回调,并将失败原因传入
    if (this.status === REJECTED) {
        onRejected(this.reason)
    }
    if (this.status === PENDING) {
        this.onFullFilledFn = onFullFilled
        this.onRejectedFn = onRejected
    }
}

Promise.then 支持链式操作

我们目前的版本最多只能注册一个回调,我们来实现支持链式回调。

  1. 首先存储回调时要改为使用数组;
  2. 执行回调时,也要改成遍历回调数组执行回调函数;
  3. then 方法返回一个 Promise。
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise {
    constructor (fn) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined

        this.onFullFilledFn = []
        this.onRejectedFn = []

        let resolve = (value) => {
            if (this.status === PENDING) {
                setTimeout(() => {
                    this.status = FULFILLED
                    this.value = value
                    this.onFullFilledFn.forEach(fn => fn(this.value))
                })
            }
        }
        let reject = (reason) => {
            if (this.status === PENDING) {
                setTimeout(() => {
                    this.status = REJECTED
                    this.reason = reason
                    this.onRejectedFn.forEach(fn => fn(this.reason))
                })
            }
        }
        try {
            fn(resolve, reject)
        } catch (err) {
            reject(err)
        }
    }
}
MyPromise.prototype.then = function (onFullFilled, onRejected) {
    return new MyPromise((resolve, reject) => {
        // 如果状态是fulfilled,直接执行成功回调,并将成功值传入
        if (this.status === FULFILLED) {
            onFullFilled(this.value)
        }
        // 如果状态是rejected,直接执行失败回调,并将失败原因传入
        if (this.status === REJECTED) {
            onRejected(this.reason)
        }
        if (this.status === PENDING) {
            this.onFullFilledFn.push(onFullFilled)
            this.onRejectedFn.push(onRejected)
        }
    })
}

支持串行异步任务

目前then方法里只能传入同步任务,但是我们平常用promise.then方法里一般是异步任务,因为我们用promise主要用来解决一组流程化的异步操作,比如:

getUserId()
    .then(getUserBalanceById)
    .then(function (balance) {
        // do sth 
    }, function (error) {
        console.log(error);
    });
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise {
    constructor (fn) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined

        this.onFullFilledFn = []
        this.onRejectedFn = []

        let resolve = (value) => {
            if (this.status === PENDING) {
                setTimeout(() => {
                    this.status = FULFILLED
                    this.value = value
                    this.onFullFilledFn.forEach(fn => fn(this.value))
                })
            }
        }
        let reject = (reason) => {
            if (this.status === PENDING) {
                setTimeout(() => {
                    this.status = REJECTED
                    this.reason = reason
                    this.onRejectedFn.forEach(fn => fn(this.reason))
                })
            }
        }
        try {
            fn(resolve, reject)
        } catch (err) {
            reject(err)
        }
    }
}
MyPromise.prototype.then = function (onFullFilled, onRejected) {
    const promise = new MyPromise((resolve, reject) => {
        // 如果状态是fulfilled,直接执行成功回调,并将成功值传入
        if (this.status === FULFILLED) {
            setTimeout(() => {
                try {
                    const x = onFullFilled(this.value)
                    resolvePromise(promise, x, resolve, reject)
                } catch(err) {
                    reject(err)
                }
            })
        }
        // 如果状态是rejected,直接执行失败回调,并将失败原因传入
        if (this.status === REJECTED) {
            setTimeout(() => {
                try {
                    const x = onRejected(this.reason)
                    resolvePromise(promise, x, resolve, reject)
                } catch(err) {
                    reject(err)
                }
            })
        }
        if (this.status === PENDING) {
            this.onFullFilledFn.push(() => {
                setTimeout(() => {
                    try {
                        const x = onFullFilled(this.value)
                        resolvePromise(promise, x, resolve, reject)
                    } catch(err) {
                        reject(err)
                    }
                })
            })
            this.onRejectedFn.push(() => {
                setTimeout(() => {
                    try {
                        const x = onRejected(this.value)
                        resolvePromise(promise, x, resolve, reject)
                    } catch(err) {
                        reject(err)
                    }
                })
            })
        }
    })
    return promise
}
function resolvePromise (promise, x, resolve, reject) {
    if (promise === x) {
        return reject(new Error('循环引用'))
    }
    // x 是除了 null 以外的对象或者函数
    let called
    if (x && (typeof x === 'function' || typeof x === 'object')) {
        try {
            const then = x.then
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return
                    called = true
                    resolvePromise(promise, y, resolve, reject)
                }, err => {
                    if (called) return
                    called = true
                    reject(err)
                })
            } else {
                resolve(x)
            }
        } catch (err) {
            if (called) return
            called = true
            reject(err)
        }
    } else {
        resolve(x)
    }
}

实现 catch / all

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise {
    constructor (fn) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined

        this.onFullFilledFn = []
        this.onRejectedFn = []
        // 在使用es6 的promise时,可以传入一个异步任务,也可以传入一个同步任务,利用setTimeout特性将具体执行放到then之后
        let resolve = (value) => {
            if (this.status === PENDING) {
                setTimeout(() => {
                    this.status = FULFILLED
                    this.value = value
                    this.onFullFilledFn.forEach(fn => fn())
                })
            }
        }
        let reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED
                this.reason = reason
                this.onRejectedFn.forEach(fn => fn())
            }
        }
        try {
            fn(resolve, reject)
        } catch (err) {
            reject(err)
        }
    }

    catch (onRejected) {
        // catch 方法就是then方法没有成功的简写
        return this.then(null, onRejected);
    }

    all (promises) {
        //promises是一个promise的数组
        return new MyPromise(function (resolve, reject) {
            let arr = []; //arr是最终返回值的结果
            let i = 0; // 表示成功了多少次
            function processData(index, data) {
                arr[index] = data;
                if (++i === promises.length) {
                    resolve(arr);
                }
            }
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(function (data) {
                    processData(i, data)
                }, reject)
            }
        })
    }
}
MyPromise.prototype.then = function (onFullFilled, onRejected) {
    const promise = new MyPromise((resolve, reject) => {
        // 如果状态是fulfilled,直接执行成功回调,并将成功值传入
        if (this.status === FULFILLED) {
            setTimeout(() => {
                try {
                    const x = onFullFilled(this.value)
                    resolvePromise(promise, x, resolve, reject)
                } catch(err) {
                    reject(err)
                }
            })
        }
        // 如果状态是rejected,直接执行失败回调,并将失败原因传入
        if (this.status === REJECTED) {
            setTimeout(() => {
                try {
                    const x = onRejected(this.reason)
                    resolvePromise(promise, x, resolve, reject)
                } catch(err) {
                    reject(err)
                }
            })
        }
        if (this.status === PENDING) {
            this.onFullFilledFn.push(() => {
                setTimeout(() => {
                    try {
                        const x = onFullFilled(this.value)
                        resolvePromise(promise, x, resolve, reject)
                    } catch(err) {
                        reject(err)
                    }
                })
            })
            this.onRejectedFn.push(() => {
                setTimeout(() => {
                    try {
                        const x = onRejected(this.value)
                        resolvePromise(promise, x, resolve, reject)
                    } catch(err) {
                        reject(err)
                    }
                })
            })
        }
    })
    return promise
}
function resolvePromise (promise, x, resolve, reject) {
    if (promise === x) {
        return reject(new Error('循环引用'))
    }
    // x 是除了 null 以外的对象或者函数
    let called
    if (x && (typeof x === 'function' || typeof x === 'object')) {
        try {
            const then = x.then
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return
                    called = true
                    resolvePromise(promise, y, resolve, reject)
                }, err => {
                    if (called) return
                    called = true
                    reject(err)
                })
            } else {
                resolve(x)
            }
        } catch (err) {
            if (called) return
            called = true
            reject(err)
        }
    } else {
        resolve(x)
    }
}

限制 Promise 并发的数量

class LimitPromise {
    constructor (max) {
        this._max = max // 异步任务“并发”上限
        this._count = 0 // 当前正在执行的任务数量
        this._taskQuequ = [] // 等待执行的任务队列
    }
    /**
     * 调用器,将异步任务函数和它的参数传入
     * @param caller 异步任务函数,它必须是async函数或者返回Promise的函数
     * @param args 异步任务函数的参数列表
     * @returns {Promise<unknown>} 返回一个新的Promise
     */
    call (caller, ...args) {
        return new Promise((resolve, reject) => {
            const task = this._createTask(caller, args, resolve, reject)
            if (this._count >= this._max) {
                this._taskQuequ.push(task)
            } else {
                task()
            }
        })
    }

    /**
     * 创建一个任务
     * @param caller 实际执行的函数
     * @param args 执行函数的参数
     * @param resolve
     * @param reject
     * @returns {Function} 返回一个任务函数
     * @private
     */
    _createTask (caller, args, resolve, reject) {
        return () => {
            caller(args)
                .then(resolve)
                .catch(reject)
                .finally(() => {
                    this._count --
                    if (this._taskQuequ.length) {
                        let task = this._taskQuequ.shift()
                        task()
                        this._count ++
                    }
                })
        }
    }
}

test code:

const LimitPromise = require('limit-promise')
const request = require('./request') 
// 请求上限 
const MAX = 10 
// 核心控制器 
const limitP = new LimitPromise(MAX) 
// 利用核心控制器包装request中的函数 
function get (url, params) {
    return limitP.call(request.get, url, params) 
} 

function post (url, params) { 
    return limitP.call(request.post, url, params) 
} 

// 导出 
module.exports = {get, post}


const request = require('./request')
request.get('https://www.baidu.com')
  .then((res) => {
    // 处理返回结果
  })
  .catch(err => {
    // 处理异常情况
  })