上一篇:Promise对象学习笔记
Promise.prototype.catch()
看一个例子:
var promise = new Promise(function(resolve, reject) { resolve('ok') setTimeout(function(){ throw new Error('test') }) }) promise.then(function(value){ console.log(value); })
这段代码执行结果如下:
上面代码中,Promise指定在下一轮“事件循环”再抛出错误。到了那个时候,Promise的运行已经结束,所以这个错误是在Promise函数体外抛出的,会冒泡到最外层,成了未捕获的错误。
这个例子跟上一篇中一个例子很像,再贴一下代码:
var promise = new Promise(function(resolve, reject) { resolve('ok') throw new Error('test') }) promise.then(function(value){ console.log(value); }).catch(function(error){ console.log(error); })
执行结果:ok
并不会抛出错误。因为Promise状态已经变成Resolved,再抛出错误是无效的。
再看一个例子
var someAsyncThing = function () { return new Promise(function(resolve, reject){ // 下面一行会报错,因为x没有声明 resolve(x + 2); }) } someAsyncThing().catch(function(error){ console.log('oh no', error) }).then(function(){ console.log('carry on') })
运行结果:
catch方法返回的还是一个Promise对象,因此后面还可以接着调用then方法
上面的代码运行完catch方法指定的回调函数后会接着运行后面那个then方法指定的回调函数。如果没有报错则跳过catch方法。
Promise.resolve().catch(function(error){ console.log('oh no', error) }).then(function(){ console.log('carry on') })
执行结果:
carry on
上面的代码因为没有报错而跳过catch方法,直接执行了后面的then方法。此时要是then方法里面报错,就与前面的catch无关了
catch方法中还能再抛出错误
var someAsyncThing = function(){ return new Promise(function(resolve, reject){ // 下面一行会报错,因为x没有声明 resolve(x + 2) }) } someAsyncThing().then(function(){ console.log('first then') }).catch(function(error){ console.log('oh no', error) // 下面一行会报错,因为y没有声明 y + 2 }).then(function(){ console.log('carry on') })
执行结果如下:
上面的代码中,catch方法抛出一个错误,因为后面没有别的catch方法,导致这个错误不会被捕获,也不会传递到外层。
改写一下代码:
var someAsyncThing = function(){ return new Promise(function(resolve, reject){ // 下面一行会报错,因为x没有声明 resolve(x + 2) }) } someAsyncThing().then(function(){ console.log('first then') }).catch(function(error){ console.log('oh no', error) // 下面一行会报错,因为y没有声明 y + 2 }).then(function(){ console.log('carry on') }).catch(function(error){ console.log('oh no', error) })
执行结果如下:
上面代码中,第二个catch方法用来捕获前一个catch方法抛出的错误。
Promise.all()
Promise.all方法用于将多个Promise实例包装成一个新的Promise实例
var p = Promise.all([p1, p2, p3])
看一个经典实用的例子
var promises = [2, 3, 5, 7, 11, 13].map(function(id){ return getJSON('/post' + id + '.json'); }) Promise.all(promises).then(function(posts){ // ... }).catch(function(reason) { // ... })
上面代码中,promises是包含6个Promise实例的数组,只有这6个实例的状态都变成fulfilled,或者其中一个变为rejected,才会调用Promise.all方法后面的回调函数。
下面是另一个例子
1 const databasePromise = connectDatabase() 2 3 const booksPromise = databasePromise.then(findAllBooks) 4 5 const userPromise = databasePromise.then(getCurrentUser) 6 7 Promise.all([booksPromise, userPromise]).then(([books, user]) => { 8 pickTopRecommentations(books, user) 9 })
上面的代码中,booksPromise和userPromise是两个异步操作,只有他们的结果都返回,才会触发pickTopRecommentations回调函数
注意:
如果作为参数的Promise实例自身定义了catch方法,那么它被rejected时并不会触发Promise.all()的catch方法
请看下面的例子
1 const p1 = new Promise(function(resolve, reject){ 2 resolve('hello') 3 }) 4 .then(result => result) 5 .catch(e => e) 6 7 const p2 = new Promise(function(resolve, reject) { 8 throw new Error('报错了') 9 }) 10 .then(result => result) 11 .catch(e => e) 12 13 Promise.all([p1, p2]) 14 .then(result => console.log(result)) 15 .catch(e => console.log(e))
执行结果:["hello", Error: 报错了]
上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的Promise实例,p2实际上指向的是这个实例。该实例执行完catch方法后也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
如果p2没有自己的catch方法,就会调用Promise.all()的catch方法
修改上面的代码:
const p1 = new Promise(function(resolve, reject){ resolve('hello') }) .then(result => result) const p2 = new Promise(function(resolve, reject) { throw new Error('报错了') }) .then(result => result) Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e))
执行结果:Error: 报错了
不会走进then方法,直接走进catch方法。
Promise.race()
Promise.race方法同样是将多个Promise实例包装成一个新的Promise实例
var p = Promise.race([p1, p2, p3])
请看一个实用例子,如果指定时间内没有获得结果,就将Promise的状态变为Rejected,否则变为Resolved
const p = Promise.race([ fetch('/resource-that-may-tabke-a-while'), new Promise(function(resole, reject){ setTimeout(() => reject(new Error('request timeout')), 5000) }) ]) p.then(response => console.log(response)) p.catch(error => console.log(error))
Promise.resolve()
有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。
var jsPromise = Promise.resolve($.ajax('/whatever.json'));
Promise.resolve方法的参数分成以下4种情况:
1.参数是一个Promise实例
如果参数是Promise实例,那么Promise.resolve将不做任何修改,原封不动地返回这个实例
2.参数是一个thenable对象
thenable对象指的是具有then方法的对象,比如下面这个对象
let thenable = { then: function (resolve, reject) { resolve(42); } }
Promise.resolve方法会将这个对象转为Promise对象,然后立即执行thenable对象的then方法
let thenable = { then: function (resolve, reject) { resolve(42); } } let p1 = Promise.resolve(thenable); p1.then(function(value){ console.log(value); })
执行结果:42
3.参数不是具有then方法的对象或根本不是对象
如果参数是一个原始值,或者是一个不具有then方法的对象,那么Promise.resolve方法返回一个新的Promise对象,状态为resolved.
var p = Promise.resolve('Hello') p.then(function(s){ console.log(s) })
执行结果:Hello
4.不带有任何参数
Promise.resolve方法允许在调用时不带有参数,而直接返回一个Resolved状态的Promise对象
var p = Promise.resolve(); p.then(function(){ // ... })
再看一个例子
setTimeout(function(){ console.log('three') }, 0) Promise.resolve().then(function(){ console.log('two') }) console.log('one')
执行结果:
one
two
three
上面代码中,setTimeout(fn, 0)是在下一轮“事件循环”开始时执行的,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。
Promise.reject()
Promise.reject(reason)方法也会返回一个新的Promise实例,状态为Rejected.
var p = Promise.reject('出错了'); // 等同于 var p = new Promise((resolve, reject) => reject('出错了')) p.then(null, function(s) { console.log(s) })
执行结果:出错了
注意:Promise.reject()方法的参数会原封不动地作为reject的理由变成后续方法的参数。这一点与Promise.resolve方法不一致。
const thenable = { then: function(resolve, reject) { reject('出错了') } } Promise.reject(thenable).catch(e => { console.log(e === thenable) }) // true
两个有用的附加方法
ES6的Promise API提供的方法不多,可以自己部署一些有用的方法。
done()
无论Promise对象的回调链以then方法还是catch方法结尾,只要最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。为此,我们可以提供一个done方法,它总是处于回调链的尾端,保证抛出任何可能出现的错误。
实现done方法的代码:
Promise.prototype.done = function(onFulfilled, onRejected) { this.then(onFulfilled, onRejected) .catch(function(reason){ // 抛出一个全局错误 setTimeout(() => {throw reason}, 0) }) }
finally()
finally方法用于指定不管Promise对象最后状态如何都会执行的操作。它与done方法的最大区别在于,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
下面是一个例子,服务器使用Promise处理请求,然后使用finally方法关掉服务器
server.listen(0) .then(function(){ // run test }) .finally(server.stop)
finally方法的实现代码:
Promise.prototype.finally = function(callback) { let P = this.constructor; return this.then( function(value) { return P.resolve(callback()).then(() => value) }, function(reason) { return P.resolve(callback()).then(() => { throw reason }) } ) }
应用
加载图片
我们可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。
const preloadImage = function (path) { return new Promise(function(resolve, reject) { var image = new Image(); image.onload = resolve; image.onError = reject; image.src = path; }) }
Promise.try()
实际开发中经常遇到一种情况:不知道或者不想区分函数f是同步函数还是异步操作,但是想用Promise来处理它。
一般写法如下:
Promise.resolve().then(f)
这种写法有个缺点,如果f是同步函数,那么它会在本轮事件循环的末尾执行。
const f = () => console.log('now') Promise.resolve().then(f) console.log('next') // 执行结果: // next // now
上面代码中,函数f是同步的,但是用Promise包装以后就变成了异步执行了。
那么有没有一种方法,让同步函数同步执行,让异步函数异步执行,并且让它们具有统一的API呢?回答是有的,并且还有两种写法。
第一种写法是使用async函数
const f = () => console.log('now'); (async () => f())() console.log('next') // 执行结果: // now // next
const f = () => console.log('now'); (async () => f())() .then(function(){ console.log('1') }) console.log('next') // 执行结果: // now // next // 1
需要注意的是,async () => f() 会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法
请看以下代码:
1 const f = () => { 2 console.log('now') 3 throw new Error('出错了') 4 }; 5 (async () => f())() 6 .then(function(){ 7 console.log('1') 8 }).catch(function(error){ 9 console.log('catch error:', error) 10 }) 11 12 console.log('next')
执行结果如下:
(end)