ES6 Promise的基本使用
什么是 Promise
Promise
是异步编程的一种解决方案,比传统的解决方案:回调函数和事件,更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象
Promise 对象两个特点:
-
1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:
- pending:(进行中)初始状态,没有变为成功或失败
- fulfilled:(已成功)意味着操作成功完成
- rejected:(已失败)意味着操作失败
一个新创建的 Promise 处于 pending 状态,当调用 resolve 或 reject 函数后,Promise 处于 fulfilled 或 rejected 状态,此后 Promise 的状态保持不变
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 的英语意思「承诺」
的由来,表示其他手段无法改变 -
2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变只有两种可能:从 pending 变为 fulfilled 或从 pending 变为 rejected。只要这两种情况发生,状态就不会再改变,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的
Promise 缺点:
- 无法取消 Promise,一旦新建它就会立即执行,中途无法取消
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部
- 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
Promise.resolve() 和 Promise.reject() 方法
使用 new 来调用 Promise 的构造函数创建 Promise 实例(Promise 实例对象被创建后就会立即执行),该构造函数会把一个叫做“处理器函数”(executor function)的函数作参数。这个“处理器函数”接受两个函数参数: resolve
和 reject
。当异步任务顺利完成且返回结果值时,会调用 resolve 函数,当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用 reject 函数
let p = new Promise(function(resolve, reject) {
//执行一些异步任务
if(/* 异步操作成功 */) {
resolve(res) //调用resolve函数,pending状态 -> fulfilled状态,将异步操作的结果,作为参数传递出去
}else {
reject(err) //调用reject函数,pending状态 -> rejected状态,将异步操作报出的错误,作为参数传递出去
}
})
Promise.prototype.then() 链式调用
then()
方法为 Promise 实例添加状态改变时的回调函数,该方法返回一个新的 Promise 实例,因此可以采用 then() 方法后面再调用另一个 then() 方法的链式写法
该方法可以接受两个回调函数作为参数:
- 第一个参数是 fulfilled 状态的回调函数
- 第二个参数(可选)是 rejected 状态的回调函数(两个函数都接受 Promise 对象传出的值作为参数)
then() 方法链式调用时,前一个 then() 方法指定的回调函数,返回一个新的 Promise 实例,后一个 then() 方法指定的回调函数,会等待这个新的 Promise 实例状态发生变化后,调用对应状态的回调函数
let p = new Promise(function(resolve, reject) {
//执行一些异步任务
if(/* 异步操作成功 */) {
resolve(res) //调用resolve函数,pending状态 -> fulfilled状态,将异步操作的结果,作为参数传递出去
}else {
reject(err) //调用reject函数,pending状态 -> rejected状态,将异步操作报出的错误,作为参数传递出去
}
})
p.then(function(res) {
console.log(res) //fulfilled状态的回调函数,接收resolve函数的值作为参数
}, function(err) {
console.log(err) //rejected状态的回调函数,接收reject函数的值作为参数
})
Promise.prototype.catch() 捕获异常
catch()
方法用于指定发生错误时的回调函数。如果 Promise 实例中异步操作抛出错误,状态变为 rejected,就会被 catch() 方法捕获到这个错误(在 resolve 函数后面抛出错误,不会被捕获到,此时 Promise 对象的状态已经改变为 fulfilled,不会再改变),调用 catch() 方法指定的回调函数做错误的处理。如果 then() 方法指定的回调函数,在运行中抛出错误,也会被 catch() 方法捕获到
该方法是 then(null, rejection) 或 then(undefined, rejection) 的别名(建议使用 catch() 方法,而不使用 then() 方法的第二个回调函数参数,因为 catch() 方法可以捕获到 then() 方法运行中抛出的错误)
let p = new Promise(function(resolve, reject) {
//执行一些异步任务,pending状态改变时执行对应的函数
if(/* 异步操作成功 */) {
resolve(res) //pending状态 -> fulfilled状态,将异步操作的结果,作为参数传递出去
}else {
reject(err) //pending状态 -> rejected状态,将异步操作报出的错误,作为参数传递出去
}
})
p.then(function(res) {
console.log(res) //fulfilled状态的回调函数,接收resolve函数的值作为参数
}).catch(function(err) {
console.log(err) //rejected状态的回调函数,接收reject函数的值作为参数(或者then()方法在运行中抛出的错误)
})
Promise.prototype.finally()
finally()
方法返回一个 Promise,在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数
这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式,也避免了同样的语句需要在 then() 和 catch() 中各写一次的情况
let p = new Promise(function(resolve, reject) {
//执行一些异步任务,pending状态改变时执行对应的函数
if(/* 异步操作成功 */) {
resolve(res) //pending状态 -> fulfilled状态,将异步操作的结果,作为参数传递出去
}else {
reject(err) //pending状态 -> rejected状态,将异步操作报出的错误,作为参数传递出去
}
})
p.then(function(res) {
console.log(res) //fulfilled状态的回调函数,接收resolve函数的值作为参数
}).catch(function(err) {
console.log(err) //rejected状态的回调函数,接收reject函数的值作为参数(或者then()方法在运行中抛出的错误)
}).finally(function() {
//promise结束时,执行的回调函数(返回状态为fulfilled或rejected)
})
解决回调地狱的问题
JavaScript 本身是单线程的,为了解决一些单线程带来的问题,异步编程成为了 JavaScript 中非常重要的一部分。当我们使用多次异步调用,异步调用的结果如果存在依赖,则会形成回调函数的嵌套,当回调函数嵌套层级过多时,就形成了回调地狱问题,回调地狱会使代码变得可读性差、难以理解和维护
let num = 1
let addNum = function(callback) {
setTimeout(function(){
num++
callback()
}, 1000)
}
//回调函数嵌套层级过多时,就形成了回调地狱问题,使代码变得可读性差、难以理解和维护
addNum(function() {
addNum(function() {
addNum(function() {
addNum(function() {
console.log(num) //5
})
})
})
})
Promise 很好的解决了回调地狱的问题,提高代码的可读性和可维护性。Promise 采用链式的 then(),可以指定一组按照次序调用的回调函数,前一个回调函数返回一个 Promise 对象(即有异步操作),后一个回调函数就会等待该 Promise 对象的状态发生变化,才会被调用
前一个回调函数完成后,会将返回结果作为参数,传入第二个回调函数
let num = 1
let addNum = function(num) {
return new Promise(function(resolve, reject) {
setTimeout(function(){
num++
resolve(num)
}, 1000)
})
}
//Promise采用链式的then()解决回调地狱问题,提高了代码的可读性和可维护性
addNum(num).then(function(res) {
return addNum(res)
}).then(function(res) {
return addNum(res)
}).then(function(res) {
return addNum(res)
}).then(function(res) {
console.log(res) //5
}).catch(function(err) {
console.log(err)
})
Promise.all()
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。该方法接受一个数组作为参数,数组的每一项都应为 Promise 实例,如果不是,就会先调用 resolve() 方法,将参数转为 Promise 实例,再进一步处理(该方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例)
Promise.all() 方法包装后的新 Promise 实例的状态由数组参数的每一项决定:
-
只有作为参数的 Promise 实例的状态都变成 fulfilled,包装后的新 Promise 实例的状态才会变成 fulfilled,此时所有作为参数的 Promise 实例的返回值组成一个数组,传递给包装后的新 Promise 实例的回调函数
let p1 = new Promise(function(resolve, reject) { setTimeout(function() { resolve(1) }, 1000) }) let p2 = new Promise(function(resolve, reject) { setTimeout(function() { resolve(2) }, 2000) }) //作为参数的Promise实例的状态都变成fulfilled //then()接受到作为参数的Promise实例的返回值的数组 Promise.all([p1, p2]).then(function(res) { console.log(res) //[1, 2] }).catch(function(err) { console.log(err) })
-
只要作为参数的 Promise 实例中有一个状态变为 rejected,包装后的新 Promise 实例的状态就会变成 rejected,此时第一个状态变为 rejected 的实例的返回值,会传递给包装后的新 Promise 实例的回调函数(注意:如果作为参数的 Promise 实例,自己定义了catch() 方法,一旦它变为 rejected 状态,并不会被 Promise.all() 的 catch() 方法捕获)
let p1 = new Promise(function(resolve, reject) { setTimeout(function() { resolve(1) }, 1000) }) let p2 = new Promise(function(resolve, reject) { setTimeout(function() { reject("error") }, 2000) }) //作为参数的Promise实例中有一个状态变为rejected //catch()捕获到第一个状态变为rejected的实例的返回值 Promise.all([p1, p2]).then(function(res) { console.log(res) }).catch(function(err) { console.log(err) //"error" })
总结:全部成功,得到成功的数组;只要有一个失败,得到第一个失败
Promise.race()
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。该方法和 Promise.all() 方法一样,接受一个数组作为参数,数组的每一项都应为 Promise 实例,如果不是,就会先调用 resolve() 方法,将参数转为 Promise 实例,再进一步处理
Promise.race() 方法包装后的新 Promise 实例的状态:
-
只要作为参数的 Promise 实例中有一个实例率先改变状态,包装后的新 Promise 实例的状态就跟着改变,那个率先改变的 Promise 实例的返回值,会传递给包装后的新 Promise 实例的回调函数
let p1 = new Promise(function(resolve, reject) { setTimeout(function() { resolve(1) }, 1000) }) let p2 = new Promise(function(resolve, reject) { setTimeout(function() { reject("error") }, 2000) }) //作为参数的Promise实例中有一个实例率先改变状态为fulfilled //then()接受到那个率先改变状态为fulfilled的Promise实例的返回值 Promise.race([p1, p2]).then(function(res) { console.log(res) //1 }).catch(function(err) { console.log(err) })
let p1 = new Promise(function(resolve, reject) { setTimeout(function() { reject("error") }, 1000) }) let p2 = new Promise(function(resolve, reject) { setTimeout(function() { resolve(2) }, 2000) }) //作为参数的Promise实例中有一个实例率先改变状态为rejected //catch()捕获到那个率先改变状态为rejected的Promise实例的返回值 Promise.race([p1, p2]).then(function(res) { console.log(res) }).catch(function(err) { console.log(err) //"error" })