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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容