Promise/A+ 学习笔记

1 什么是 Promise

Promise 是前端流行的异步编程解决方案,而Promise/A+ 是一组关于 Promise 实现的开放标准。

2 如何使用 Promise

构建 Promise 对象,传入异步执行过程,描述何时解决(resolve)、何时拒绝(reject)

var p1 = new Promise(function(resolve, reject) {
    // 模拟异步操作
    setTimeout(function() {
        if ( /* 执行成功时 */ ) {
            resolve(value)
        } else if ( /* 执行失败时 */ ) {
            reject(reason)
        }
    }, 2000)
})

在 Promise 上调用 then 方法,传入接收 解决结果/拒绝原因 的回调函数

p1.then(
    function(value) { 
        /* 使用“解决结果” */ 
    },
    function(reason) { 
        /* 使用“拒绝原因” */ 
    }
)

then 可被多次调用,且多次调用返回的结果一致

p1.then(onFulfilled1, onRejected1)
p1.then(onFulfilled2, onRejected2)
p1.then(onFulfilled3, onRejected3)

由于 then 每次调用都会返回一个新的 promise 对象,所以 then 可被链式调用

p1
.then(onFulfilled1, onRejected1)
.then(onFulfilled2, onRejected2)
.then(onFulfilled3, onRejected3)

3 Promise/A+ 规范与实现

3.1 Promise 的三种状态
  • pending(等待中)
  • fulfilled(已完成)
  • rejected(已拒绝)

新生成的 Promise 处于 pending 状态,依据用户的代码逻辑,可能变成 fulfilled 或 rejected。
Promise 一旦处于 fulfilled 或 rejected 状态,就无法再被改变。

代码实现如下:

var PENDING = 'pending'
var FULFILLED = 'fulfilled'
var REJECTED = 'rejected'

function Promise(fn) {
    var that = this

    that.status = PENDING
    that.value = void 0   // 保存 resolve 调用时传入的 value
    that.reason = void 0  // 保存 reject 调用时传入的 reason

    var resolve = function(value) {
        if (that.status === PENDING) {
            that.status = FULFILLED
            that.value = value
        }
    }

    var reject = function(reason) {
        if (that.status === PENDING) {
            that.status = REJECTED
            that.reason = reason
        }
    }

    fn(resolve, reject)
}
3.2 promise.then 详解
  • then 方法包含两个可选参数 promise.then(onFulfilled, onRejected)

  • then 方法调用后必须返回一个新的 promise var newPromise = promise.then(onFulfilled, onRejected)

  • onFulfilled / onRejected 的类型如果不是 function,忽略该参数

  • onFulfilled 必须在 promise 的状态变成 fulfilled 后被调用,并将 value 作为第一个参数传入

  • onRejected 必须在 promise 的状态变成 rejected 后被调用,并将 reason 作为第一个参数传入

  • onFulfilled / onRejected 必须是异步执行的

    Promise.prototype.then = function(onFulfilled, onRejected) {
        onFulfilled = (typeof onFulfilled === 'function') ? onFulfilled : function() { }
        onRejected = (typeof onRejected === 'function') ? onRejected : function() { }
    
        var that = this
        var newPromise = null
    
        if (that.status === FULFILLED) {
            newPromise = new Promise(function(resolve, reject) {
                // 确保异步调用
                setTimeout(function() {
                    try {
                        var x = onFulfilled(that.value)
                    } catch (e) {
                        reject(e)
                    }
                })
            })
        }
    
        if (that.status === REJECTED) {
            newPromise = new Promise(function(resolve, reject) {
                // 确保异步调用
                setTimeout(function() {
                    try {
                        var x = onRejected(that.reason)
                    } catch (e) {
                        reject(e)
                    }
                })
            })
        }
    
        if (that.status === PENDING) {
            newPromise = new Promise(function(resolve, reject) {
                // 等待 status 变成 FULFILLED/REJECTED 才能调用
            })
        }
    
        return newPromise
    }
    

通常 then 方法调用时,promise 仍处于 pending 状态,那么必须推迟 onFulfilled/onRejected 的调用时机,使得 promise 的状态变成 fulfilled/rejected 后 onFulfilled/onRejected 才能被调用。
并且,如果 then 方法被调用多次,onFulfilled/onRejected 的调用顺序必须与 then 方法被调用的顺序一致。

Promise 的改动:

// 为 promise 保存 onFulfilled/onRejected 回调函数的队列
that.fulfilledCallbacks = []
that.rejectedCallbacks = []

var resolve = function(value) {
    if (that.status === PENDING) {
        that.status = FULFILLED
        that.value = value
        // pending 时加入的 onFulfilled 队列按顺序调用
        that.fulfilledCallbacks.length > 0 && that.fulfilledCallbacks.forEach(function(callback) {
            callback()
        })
    }
}

var reject = function(reason) {
    if (that.status === PENDING) {
        that.status = REJECTED
        that.reason = reason
        // pending 时加入的 onRejected 队列按顺序调用
        that.rejectedCallbacks.length > 0 && that.rejectedCallbacks.forEach(function(callback) {
            callback()
        })
    }
}

Promise.prototype.then 的改动:

if (that.status === PENDING) {
    newPromise = new Promise(function(resolve, reject) {
        // 等待 status 变成 FULFILLED/REJECTED 才能调用
        that.fulfilledCallbacks.push(function() {
            setTimeout(function() {
                try {
                    var x = onFulfilled(that.value)
                } catch (e) {
                    reject(e)
                }
            })
        })
        that.rejectedCallbacks.push(function() {
            setTimeout(function() {
                try {
                    var x = onRejected(that.reason)
                } catch (e) {
                    reject(e)
                }
            })
        })
    })
}

如果 onFulfilled/onRejected 的类型不是 function,那么将对应的 value/reason 传递下去。

Promise.prototype.then 的改动:

onFulfilled = (typeof onFulfilled === 'function') ? onFulfilled : function(value) { return value }
onRejected = (typeof onRejected === 'function') ? onRejected : function(reason) { throw reason }
3.3 [[Resolve]](promise, x) 详解

由于 Promise 是先有社区实现,再逐渐形成规范,许多早期实现的 Promise 库与规范并不完全一致。
例如:jQuery.Deferred()
为了兼容这些并不完全符合规范的实现,[[Resolve]](promise, x) 有如下要求:

  • promise 与 x 不能是相等的值或同一个对象
  • x 必须是一个 thenable 对象
function resolvePromise(promise, x, resolve, reject) {
    if (promise === x) {
        return reject(new TypeError('promise === x!!'))
    }

    // 依据规范明细 2.3.3
    var called = false
    if (!!x && (typeof x === 'function' || typeof x === 'object')) {
        try {
            var then = x.then
            if (typeof then === 'function') {
                then.call(x, function(y) {
                    if (called) return
                    called = true
                    resolvePromise(promise, y, resolve, reject)
                }, function(r) {
                    if (called) return
                    called = true
                    reject(r)
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true
            reject(e)
        }
    } else {
        resolve(x)
    }
}

Promise.prototype.then 的改动:

if (that.status === FULFILLED) {
    newPromise = new Promise(function(resolve, reject) {
        // 确保异步调用
        setTimeout(function() {
            try {
                var x = onFulfilled(that.value)
                // [[Resolve]](promise, x) 
                resolvePromise(newPromise, x, resolve, reject)
            } catch (e) {
                reject(e)
            }
        })
    })
}

if (that.status === REJECTED) {
    newPromise = new Promise(function(resolve, reject) {
        // 确保异步调用
        setTimeout(function() {
            try {
                var x = onRejected(that.reason)
                // [[Resolve]](promise, x) 
                resolvePromise(newPromise, x, resolve, reject)
            } catch (e) {
                reject(e)
            }
        })
    })
}

if (that.status === PENDING) {
    newPromise = new Promise(function(resolve, reject) {
        // 等待 status 变成 FULFILLED/REJECTED 才能调用
        that.fulfilledCallbacks.push(function() {
            setTimeout(function() {
                try {
                    var x = onFulfilled(that.value)
                    // [[Resolve]](promise, x) 
                    resolvePromise(newPromise, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            })
        })
        that.rejectedCallbacks.push(function() {
            setTimeout(function() {
                try {
                    var x = onRejected(that.reason)
                    // [[Resolve]](promise, x) 
                    resolvePromise(newPromise, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            })
        })
    })
}

这样,我们就实现了一个符合 Promise/A+ 规范的 Promise 库。

4 测试

那么,怎么才能验证我们实现的 Promise 库确实是完全符合规范的呢?
规范提供了测试套件 test suit,依据该测试套件的要求,我们还需要进行一点小小的修改。

// 封装下述 API 用于测试

Promise.deferred = Promise.defer = function() {
    var dfd = {}
    dfd.promise = new Promise(function(fulfill, reject) {
        dfd.resolve = fulfill
        dfd.reject = reject
    })
    return dfd
}

Promise.resolve = function(value) {
    var promise = new Promise(function(fulfill, reject) {
        resolvePromise(promise, value, fulfill, reject)
    })
    return promise
}

Promise.reject = function(reason) {
    return new Promise(function(fulfill, reject) {
        reject(reason)
    })
}

module.exports = Promise

然后,使用 npm 安装测试库 npm install --save-dev promises-aplus-tests,编写简单的测试代码并运行

var promisesAplusTests = require("promises-aplus-tests")
var adapter = requirel("./MyPromise.js")

promisesAplusTests(adapter, function (err) {
    // All done; output is in the console. Or check `err` for number of failures.
});
测试结果,完全符合规范

最后,附上代码全文:

var PENDING = 'pending'
var FULFILLED = 'fulfilled'
var REJECTED = 'rejected'

function Promise(fn) {
    var that = this

    that.status = PENDING
    that.value = void 0  // 保存 resolve 调用时传入的 value
    that.reason = void 0  // 保存 reject 调用时传入的 reason

    // 为 promise 保存 onFulfilled/onRejected 回调函数的队列
    that.fulfilledCallbacks = []
    that.rejectedCallbacks = []

    var resolve = function(value) {
        if (that.status === PENDING) {
            that.status = FULFILLED
            that.value = value
            // pending 时加入的 onFulfilled 队列按顺序调用
            that.fulfilledCallbacks.length > 0 && that.fulfilledCallbacks.forEach(function(callback) {
                callback()
            })
        }
    }

    var reject = function(reason) {
        if (that.status === PENDING) {
            that.status = REJECTED
            that.reason = reason
            // pending 时加入的 onRejected 队列按顺序调用
            that.rejectedCallbacks.length > 0 && that.rejectedCallbacks.forEach(function(callback) {
                callback()
            })
        }
    }

    try {
        fn(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

function resolvePromise(promise, x, resolve, reject) {
    if (promise === x) {
        return reject(new TypeError('promise === x!!'))
    }

    // 依据规范明细 2.3.3
    var called = false
    if (!!x && (typeof x === 'function' || typeof x === 'object')) {
        try {
            var then = x.then
            if (typeof then === 'function') {
                then.call(x, function(y) {
                    if (called) return
                    called = true
                    resolvePromise(promise, y, resolve, reject)
                }, function(r) {
                    if (called) return
                    called = true
                    reject(r)
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true
            reject(e)
        }
    } else {
        resolve(x)
    }
}

Promise.prototype.then = function(onFulfilled, onRejected) {
    onFulfilled = (typeof onFulfilled === 'function') ? onFulfilled : function(value) { return value }
    onRejected = (typeof onRejected === 'function') ? onRejected : function(reason) { throw reason }

    var that = this
    var newPromise = null

    if (that.status === FULFILLED) {
        newPromise = new Promise(function(resolve, reject) {
            // 确保异步调用
            setTimeout(function() {
                try {
                    var x = onFulfilled(that.value)
                    resolvePromise(newPromise, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            })
        })
    }

    if (that.status === REJECTED) {
        newPromise = new Promise(function(resolve, reject) {
            // 确保异步调用
            setTimeout(function() {
                try {
                    var x = onRejected(that.reason)
                    resolvePromise(newPromise, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            })
        })
    }

    if (that.status === PENDING) {
        newPromise = new Promise(function(resolve, reject) {
            // 等待 status 变成 FULFILLED/REJECTED 才能调用
            that.fulfilledCallbacks.push(function() {
                // 确保异步调用
                setTimeout(function() {
                    try {
                        var x = onFulfilled(that.value)
                        resolvePromise(newPromise, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                })
            })
            that.rejectedCallbacks.push(function() {
                // 确保异步调用
                setTimeout(function() {
                    try {
                        var x = onRejected(that.reason)
                        resolvePromise(newPromise, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                })
            })
        })
    }

    return newPromise
}

Promise.deferred = Promise.defer = function() {
    var dfd = {}
    dfd.promise = new Promise(function(fulfill, reject) {
        dfd.resolve = fulfill
        dfd.reject = reject
    })
    return dfd
}

Promise.resolve = function(value) {
    var promise = new Promise(function(fulfill, reject) {
        resolvePromise(promise, value, fulfill, reject)
    })
    return promise
}

Promise.reject = function(reason) {
    return new Promise(function(fulfill, reject) {
        reject(reason)
    })
}

module.exports = Promise

你可能感兴趣的:(Promise/A+ 学习笔记)