Promise是抽象异步处理对象以及对其进行各种操作的组件。
API 三种类型:
-
Constructor
从构造函数 Promise 来创建一个新建新 promise 对象作为接口。
要想创建一个promise对象、可以使用 new 来调用 Promise 的构造器来进行实例化var promise = new Promise((resolve, reject) => { // 异步处理 // 处理结束后、调用resolve 或 reject })
-
Instance Method
对通过new生成的promise对象为了设置其值在 resolve(成功) / reject(失败)时调用的回调函数 可以使用 promise.then() 实例方法- promise.then(onFulfilled, onRejected)
- resolve(成功)时, onFulfilled 会被调用
- reject(失败)时, onRejected 会被调用
只想对异常进行处理时,更好的选择是使用promise.catch(onRejected)
Static Method
包括 Promise.all() 还有 Promise.resolve() 等在内,主要都是一些对Promise进行操作的辅助方法。
Promise workflow
示例代码:
function asyncFunction() {
// new Promise 构造器之后,会返回一个promise对象
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Async Hello world')
}, 16)
})
}
// asyncFunction这个函数会返回promise对象。对于这个promise对象,我们调用它的then方法来设置resolve后的回调函数, catch方法来设置发生错误时的回调函数。
asyncFunction()
.then(value => {
console.log(value) // => 'Async Hello world'(在这种情况下 catch 的回调函数并不会被执行(因为promise返回了resolve))
})
.catch(err => {
console.log(err) // 如果运行环境没有提供 setTimeout 函数的话,那么上面代码在执行中就会产生异常,在catch 中设置的回调函数就会被执行。
})
Promise的状态
用 new Promise 实例化的promise对象有以下三个状态:
- "has-resolution" - Fulfilled
resolve(成功)时。此时会调用 onFulfilled - "has-rejection" - Rejected
reject(失败)时。此时会调用 onRejected - "unresolved" - Pending
既不是resolve也不是reject的状态。也就是promise对象刚被创建后的初始化状态等
promise对象的状态,从Pending转换为Fulfilled或Rejected之后, 这个promise对象的状态就不会再发生任何变化。也就是说,Promise与Event等不同,在 .then 后执行的函数可以肯定地说只会被调用一次。
另外,Fulfilled和Rejected这两个中的任一状态都可以表示为Settled(不变的)。
Settled: resolve(成功) 或 reject(失败)。
创建promise对象的流程如下所示。
- new Promise(fn) 返回一个promise对象
- 在 fn 中指定异步等处理
- 处理结果正常的话,调用 resolve(处理结果值)
- 处理结果错误的话,调用 reject(Error对象)
实例:用Promise来通过异步处理方式来获取XMLHttpRequest(XHR)的数据。
function getURL(url) {
return new Promise((resolve, reject) => {
const req = new XMLHttpRequest()
req.open('GET', url, true)
req.onload = () => {
if (req.status === 200) {
resolve(req.responseText)
} else {
reject(new Error(req.statusText))
}
}
req.onerror = () => {
reject(new Error(req.statusText))
}
req.send()
})
}
// 运行
const url = 'https://www.google.com.hk'
getURL(url)
.then(onFulfilled = value => {
console.log(value)
})
.catch(onRejected = error => {
console.error(error)
})
// 其实,.catch 只是 promise.then(undefined, onRejected) 的别名而已, 如下代码也可以完成同样的功能。
getURL(url)
.then(onFulfilled, onRejected)
Promise.resolve
- new Promise的快捷方式
静态方法 Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。
比如 Promise.resolve(42) 可以认为是以下代码的语法糖。
new Promise(resolve => {
resolve(42)
})
// 方法 Promise.resolve(value) 的返回值也是一个Promise对象,所以我们可以像下面那样接着对其返回值进行 .then 调用。
Promise.resolve(42)
.then(value => {
console.log(value)
})
-
将thenable对象转换Promise对象
这种机制要求thenable对象所拥有的 then 方法应该和Promise所拥有的 then 方法具有同样的功能和处理过程,在将thenable对象转换为promise对象的时候,还会巧妙的利用thenable对象原来具有的 then 方法。
实例: jQuery.ajax(),它的返回值就是thenable的(这个对象具有 .then 方法)。$.ajax('/json/comment.json') // => 拥有 .then 方法的对象 // 这个thenable的对象可以使用 Promise.resolve 来转换为一个promise对象 const promise = Promise.resolve($.ajax('/json/comment.json')) // => promise对象 promise.then(value => { console.log(value) })
Promise.reject
Promise.reject(error) 是和 Promise.resolve(value) 类似的静态方法,是 new Promise() 方法的快捷方式。
比如 Promise.reject(new Error('error')) 是以下代码的语法糖。
new Promise((resolve, reject) => {
reject(new Error('error'))
})
// 这段代码的功能是调用该Promise对象通过then指定的 onRejected 函数,并将错误(Error)对象传递给这个 onRejected 函数。
Promise.reject(new Error('BOOM!'))
.catch(err => {
console.error(err)
})
避免对异步回调函数进行同步调用
实例:根据执行时DOM是否已经装载完毕来决定是对回调函数进行同步调用还是异步调用。
function onReady(fn) {
const readyState = document.readyState
if (readyState === 'interactive' || readyState === 'complete') {
setTimeout(fn, 0)
} else {
window.addEventListener('DOMContentLoaded', fn)
}
}
onReady(() => {
console.log('DOM fully loaded and parsed')
})
console.log('==Starting==')
// 用Promise重写
function onReadyPromise() {
return new Promise((resolve, reject) => {
const readyState = document.readyState
if (readyState === 'interactive' || readyState === 'complete') {
resolve()
} else {
window.addEventListener('DOMContentLoaded', resolve)
}
})
}
onReadyPromise()
.then(() => {
console.log('DOM fully loaded and parsed')
})
console.log('==Starting==')
Promise chain
- Promise#then
- Promise#catch
promise chain 中如何传递参数
实例:如果 Task A 想给 Task B 传递一个参数,那就在 Task A 中 return 的返回值,会在 Task B 执行时传给它。
function doubleUp(value) {
return value * 2
}
function increment(value) {
return value + 1
}
function output(value) {
console.log(value) // => (1 + 1) * 2
}
const promise = Promise.resolve(1)
promise
.then(increment)
.then(doubleUp)
.then(output)
.catch(err => {
// promise chain中出现异常的时候会被调用
console.error(err)
})
return的值会由 Promise.resolve(return的返回值) 进行相应的包装处理,因此不管回调函数中会返回一个什么样的值,最终 then 的结果都是返回一个新创建的Promise对象。
使用Promise同时处理多个异步请求
在多个promise对象都变为FulFilled状态的时候才要进行某种处理时:
-
Promise#then
function getURL(URL) { return new Promise((resolve, reject) => { const req = new XMLHttpRequest() req.open('GET', URL, true) req.onload = () => { if (req.status === 200) { resolve(req.responseText) } else { reject(new Error(req.statusText)) } } req.onerror = () => { reject(new Error(req.statusText)) } req.send() }) } const request = { comment: function getComment() { return getURL('http://azu.github.io/promises-book/json/comment.json') .then(JSON.parse) }, people: function getPeople() { return getURL('http://azu.github.io/promises-book/json/people.json') .then(JSON.parse) } } function main() { function recordValue(results, value) { results.push(value) return results } // [] 用来保存初始化的值 const pushValue = recordValue.bind(null, []) return request.comment() .then(pushValue) .then(request.people) .then(pushValue) } // 运行的例子 main() .then(value => { console.log(value) }) .catch(err => { console.error(err) })
为了应对这种需要对多个异步调用进行统一处理的场景,Promise准备了 Promise.all 和 Promise.race 这两个静态方法。
-
Promise.all
Promise.all 接收一个 Promise对象的数组作为参数,当这个数组里的所有Promise对象全部变为resolve或reject状态的时候,它才会去调用 .then 方法。
之前例子中的 getURL 返回了一个promise对象,它封装了XHR通信的实现。 向 Promise.all 传递一个由封装了XHR通信的promise对象数组的话,则只有在全部的XHR通信完成之后(变为FulFilled或Rejected状态)之后,才会调用 .then 方法。
上面的例子用Promise.all改写(除main()以外其他部分不变)如下:function main() { return Promise.all([request.comment(), request.people()]) }
与之前的例子相比:
- main中的处理流程显得非常清晰
- Promise.all 接收 promise对象组成的数组作为参数
在上面的代码中,request.comment() 和 request.people() 会同时开始执行,而且每个promise的结果(resolve或reject时传递的参数值),和传递给Promise.all 的promise数组的顺序是一致的。
也就是说,这时候 .then 得到的promise数组的执行结果的顺序是固定的,即
[comment, people]。
main()
.then(results => {
console.log(results) // 按照[comment, people]的顺序
})
传递给 Promise.all 的promise并不是一个个的顺序执行的,而是同时开始、并行执行的。
-
Promise.race
Promise.all 在接收到的所有的对象promise都变为 FulFilled 或者 Rejected 状态之后才会继续进行后面的处理,与之相对的是 Promise.race 只要有一个promise对象进入FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。const winnerPromise = new Promise(resolve => { setTimeout(() => { console.log('this is winner') resolve('this is winner --resolve') }, 0) }) const loserPromise = new Promise(resolve => { setTimeout(() => { console.log('this is loser') resolve('this is loser --resolve') }, 0) }) // 第一个promise变为resolve(FulFilled)后程序停止 Promise.race([winnerPromise, loserPromise]) .then(function (value) { console.log(value) // => 'this is winner' }) // 输出结果 this is winner this is loser this is winner --resolve
winnter和loser promise对象的 setTimeout 方法都会执行完毕,console.log 也会分别输出它们的信息。
也就是说,Promise.race 在第一个promise对象变为Fulfilled之后,并不会取消其他promise对象的执行。 -
总结
- 使用 promise.then(onFulfilled, onRejected)
在 onFulfilled 中发生异常的话,在 onRejected 中是捕获不到这个异常的。 - 在 promise.then(onFulfilled).catch(onRejected) 的情况下
.then 中产生的异常能在 .catch 中捕获 - .then 和 .catch 在本质上是没有区别的
需要分场合使用。.then(null, onRejected) 等同于.catch(onRejected)
- 使用 promise.then(onFulfilled, onRejected)