本篇文章是Promise
系列文章的第二篇,主要是讲解基于Promise/A+
规范,在传入不同类型的参数时,promise
内部分别会如何处理。本章的主要目的是让大家对promise
有一个更加深入的理解,也为下一篇讲如何实现一个promise
库做准备。(写完之后觉得好水。。。)
英文版本的规范见这里,segmentfault上也有人把规范翻译为中文,见这里。
在此,我主要是通过使用例子,讲解一下规范中then
方法和Promise Resolution Procedure
的每一种情况。
constructor
规范中对于构造函数没有明确说明,所以在此处拿出来讲解一下。
和普通JavaScript对象一样,我们同样是通过new关键词来创建一个Promise
对象实例。构造函数只接收一个参数,且该参数必须是一个函数,任何其他的值比如undefined
、null
、5
、true
等都会报一个TypeError
的错误。例:
new Promise(true)
// Uncaught TypeError: Promise resolver true is not a function(…)
同样,如果你没有通过new关键词创建,而是直接执行Promise(),同样也会报一个TypeError
的错误。
Promise()
// Uncaught TypeError: undefined is not a promise(…)
所以,我们必须通过new Promise(function()=>{})
的方式来创建一个Promise实例。通常我们见到的创建一个Promise实例的代码如下:
var promise = new Promise(function(resolve, reject) {
// 进行一些异步操作
// 然后调用resolve或reject方法
});
这才是正确的姿势~ 从该例子中,我们可以看到创建Promise实例时传入的函数,同时还接受两个参数,它们分别对应Promise内部实现的两个方法。上一篇文章中,我提到过Promise有三种状态,pending
、fulfilled
、rejected
,实例刚创建时处于pending
状态,当执行reject
方法时,变为rejected
状态,如下所示:
new Promise(function(resolve, reject){
reject(Promise.resolve(5))
}).then(function(value){
console.log('fulfill', value)
}, function(reason){
console.log('reject', reason)
})
// reject Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 5}
而当执行resolve
方法时,它可能变为fulfilled
,也有可能变为rejected
状态。也就是说resolve != fulfill
。如下:
new Promise(function(resolve, reject){
resolve(Promise.reject(5))
}).then(function(value){
console.log('fulfill', value)
}, function(reason){
console.log('reject', reason)
})
// reject 5
那么resolve
是个什么东西呢?它是根据什么变为fulfilled
或rejected
的呢?这就是我们接下来要讲解的Promise Resolution Procedure
,我把它称作“Promise处理程序”。
Promise Resolution Procedure
讲之前,我们先说几个promise
规范中的几个术语。
promise 它是一个拥有then
方法的对象或函数,且符合该规范
thenable 拥有then
方法的对象或函数
value 是指一个合法的 Javascript
值
exception throw
语句抛出的异常
reason 描述promise为什么失败的值
Promise Resolution Procedure
是对传入的promise和value进行抽象操作。我们可一个把它理解成resolve(promise, value)
,对参数promise和value进行一系列处理操作。下面我们按照规范中的顺序,依次介绍每种情况。
2.3.1 如果promise
和value
指向同一个对象,则reject
该promise
并以一个TypeError
作为reason
。
var defer = {}
var promise = new Promise(function(resolve){
defer.resolve = resolve
})
promise.catch(function(reason){
console.log(reason)
})
defer.resolve(promise)
// TypeError: Chaining cycle detected for promise #<Promise>(…)
我们把resolve函数保存在defer中,这样就可以在外部对promise
进行状态改变,defer.resolve(promise)
中的promise
正是我们创建的对象,根据规范抛出了TypeError
。
2.3.2 如果value
是一个promise
对象,且是基于当前实现创建的。
2.3.2.1 如果value
处于pending
状态,则promise
同样pending
并直到value
状态改变。
2.3.2.2 如果value
处于fulfilled
状态,则使用相同的value值fulfill promise
。
2.3.2.3 如果value
处于rejected
状态,则使用相同的reason值reject promise
。
var promise1 = new Promise((resolve) => {
setTimeout(() => {
resolve(5)
},3000)
});
console.time('fulfill')
var promise = new Promise((resolve) => {
resolve(promise1)
})
promise.then((value) => {
console.timeEnd('fulfill')
console.log('fulfill', value)
})
setTimeout(()=>{
console.log('setTimeout', promise)
}, 1000)
// setTimeout Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// fulfill: 3.01e+03ms
// fulfill 5
通过该例子可以看出,最后setTimeout
延迟1秒查看promise
状态时,它依然处于pending
状态,当3秒后promise1
变为fulfilled
后,promise
随即变为fulfilled
并以5作为value传给then
添加的成功回调函数中。
var promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('error'))
}, 3000)
});
console.time('reject')
var promise = new Promise((resolve) => {
resolve(promise1)
})
promise.catch((reason) => {
console.timeEnd('reject')
console.log('reject', reason)
})
setTimeout(()=>{
console.log('setTimeout', promise)
}, 1000)
// setTimeout Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// reject: 3e+03ms
// reject Error: error(…)
失败时例子与成功时类似。
2.3.3 如果value
是一个对象或函数
2.3.3.1 使then
等于value.then
2.3.3.2 如果获取value.then
的值时抛出异常,这通过该异常reject
promise
,例:
new Promise((resolve)=>{
resolve({then:(()=>{
throw new Error('error')
})()
})
}).catch((reason)=>{
console.log(reason)
})
// Error: error(…)
上例中获取value.then
时,会抛出异常
2.3.3.3 如果then
是一个函数,则把value
作为函数中this
指向来调用它,第一个参数是resolvePromise
,第二个参数是rejectPromise
。
其实这里主要是为了兼容两种情况,第一种是传入的value
是个Deferred
对象,则状态和Deferred
对象一致;另一种情况是不是使用当前构造函数创建的Promise
对象,通过这种方式可以兼容,达到一致的效果。
2.3.3.3.1 如果resolvePromise
通过传入y
来调用,则执行resolve(promise, y)
,例:
new Promise((resolve)=>{
resolve({then:(resolvePromise, rejectPromise)=>{
resolvePromise(5)
}
})
}).then((value)=>{
console.log(value)
})
// 5
2.3.3.3.2 如果rejectPromise
通过传入原因r
来调用,则传入r
来reject
promise
,例:
new Promise((resolve)=>{
resolve({then:(resolvePromise, rejectPromise)=>{
rejectPromise(new Error('error'))
}
})
}).catch((reason)=>{
console.log(reason)
})
// Error: error(…)
2.3.3.3.3 如果resolvePromise
和rejectPromise
都被调用,或其中一个被调用了多次,则以第一次调用的为准,并忽略之后的调用。例:
new Promise((resolve)=>{
resolve({then:(resolvePromise, rejectPromise)=>{
resolvePromise(5)
rejectPromise(new Error('error'))
}
})
}).then((value)=>{
console.log(value)
}, (reason)=>{
console.log(reason)
})
// 5
2.3.3.3.4 如果调用then
抛出异常e
:
2.3.3.3.4.1 如果resolvePromise
或rejectPromise
已经调用,则忽略它,例:
new Promise((resolve)=>{
resolve({then:(resolvePromise, rejectPromise)=>{
resolvePromise(5)
throw new Error('error')
}
})
}).then((value)=>{
console.log(value)
}, (reason)=>{
console.log(reason)
})
// 5
2.3.3.3.4.2 否则,则传入e
来reject
promise
,例:
new Promise((resolve)=>{
resolve({then:(resolvePromise, rejectPromise)=>{
throw new Error('error')
}
})
}).then((value)=>{
console.log(value)
}, (reason)=>{
console.log(reason)
})
// Error: error(…)
2.3.3.4 如果then
不是一个函数,则传入value
来fulfill
promise
,例:
new Promise((resolve)=>{
resolve({then:5})
}).then((value)=>{
console.log(value)
}, (reason)=>{
console.log(reason)
})
// Object {then: 5}
then
方法
一个promise
必须提供一个then
方法来处理成功或失败。
then
方法接收两个参数:
promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled
和onRejected
都是可选的
2.2.1.1 如果onFulfilled
不是一个函数,则忽略。例:
Promise.resolve(5)
.then(true,function(reason){
console.log(reason)
})
.then(function(value){
console.log(value)
})
// 5
2.2.1.2 如果onRejected
不是一个函数,则忽略。例:
Promise.reject(new Error('error'))
.then(true,null)
.then(undefined,function(reason){
console.log(reason)
})
// Error: error(…)
2.2.2 如果onFulfilled
是一个函数
2.2.2.1 它必须在promise
变为fulfilled
之后调用,且把promise
的value
作为它的第一个参数
这个从我们所有的例子中都可以看出
2.2.2.2 它不可以在promise
变为fulfilled
之前调用
var defer = {}
console.time('fulfill')
var promise = new Promise((resolve)=>{
defer.resolve = resolve
});
promise.then((value)=>{
console.timeEnd('fulfill')
})
setTimeout(()=>{
defer.resolve(5)
},1000);
// fulfill: 1e+03ms
从onFulfilled
执行的时间可以看出promise
直到变为fulfilled
后才调用
2.2.2.3 它只可以被调用一次
var defer = {}
var promise = new Promise((resolve)=>{
defer.resolve = resolve
});
promise.then((value)=>{
console.log(value++)
})
defer.resolve(5)
// 5
defer.resolve(6)
// 后面不再次执行
2.2.3 如果onRejected
是一个函数
2.2.3.1 它必须在promise
变为rejected
之后调用,且把promise
的reason
作为它的第一个参数
2.2.3.2 它不可以在promise
变为rejected
之前调用
2.2.3.3 它只可以被调用一次
onRejected
和onFulfilled
基本类似,这里不再次赘述
2.2.4 onFulfilled
和onRejected
是在执行环境中仅包含平台代码时调用
这里有一个备注,平台代码是指引擎、执行环境、以及promise
的实现代码。实际过程中,要确保onFulfilled
和onRejected
是异步执行的,它是在event loop
过程中then
方法被调用之后的新调用栈中执行。我们可以使用setTimeout
或setImmediate
等macro-task
机制来实现,也可以使用MutationObserver
或process.nextTick
等micro-task
机制来实现。promise
的实现本身就被看作是平台代码,它本身就包含一个处理器可以调用的任务调度队列。
才疏学浅,没理解它这一条到底要表达一个什么意思。。。应该指的就是异步执行,因为异步执行的时候,页面中同步的逻辑都已经执行完毕,所以只剩下平台代码。
注:原生的Promise
实现属于micro-task
机制。macro-task
和micro-task
分别是两种异步任务,它们的不同后面会单独讲一下。下面列出了常见的异步方法都属于那种异步机制:
macro-task: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
micro-task: process.nextTick, 原生Promise, Object.observe, MutationObserver
2.2.5 onFulfilled
和onRejected
必须作为函数来调用,没有this
值
Promise.resolve(5).then(function(){
console.log(this)
})
// Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage, sessionStorage: Storage, webkitStorageInfo: DeprecatedStorageInfo…}
2.2.6 同一个promise
上的then
方法可能会调用多次
2.2.6.1 如果promise
fulfilled
,则所有的onFulfilled
回调函数按照它们添加的顺序依次调用。
var defer = {}
var promise = new Promise((resolve)=>{
defer.resolve = resolve
});
promise.then((value)=>{
console.log(1,value++)
})
promise.then((value)=>{
console.log(2,value++)
})
promise.then((value)=>{
console.log(3,value++)
})
defer.resolve(5)
// 1 5
// 2 5
// 3 5
2.2.6.2 如果promise
rejected
,则所有的onRejected
回调函数按照它们添加的顺序依次调用。
例子与上例类似
2.2.7 then
方法会返回一个全新的promise
promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.1 如果onFulfilled
或onRejected
返回了一个值x
,则执行resolve(promise2, x)
Promise.resolve(5).then(function(value){
return ++value
}).then(function(value){
console.log(value)
})
// 6
2.2.7.2 如果onFulfilled
或onRejected
抛出了异常e
,则reject
promise2
并传入原因e
Promise.resolve(5).then(function(value){
throw new Error('error')
}).catch(function(reason){
console.log(reason)
})
// Error: error(…)
2.2.7.3 如果onFulfilled
不是一个函数且promise1
fulfilled
,则promise2
以同样的value
fulfill
Promise.resolve(5).then("tiaoguo").then(function(value){
console.log(value)
})
// 5
2.2.7.4 如果onRejected
不是一个函数且promise1
rejected
,则promise2
以同样的reason
reject
Promise.reject(new Error('error')).catch('tiaoguo').catch(function(reason){
console.log(reason)
})
// Error: error(…)
更多的测试代码,大家可以去promises-tests中查看,这是一个基于规范的promise
测试库。
以上基本是整个Promise/A+
规范的所有内容,如有错误,欢迎批评指正。下一篇我会根据规范一步一步实现一个Promise
polyfill
库。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。