在 Promise 出现以前,我们处理一个异步网络请求,需求大概是这样:我们需要根据第一个网络请求的结果,再去执行第二个网络请求,再根据第二个网络请求的结果去执行第三个请求~,需求是永无止境的,于是乎出现了如下代码:
请求1(function(){
// 一些其他操作
请求2(function(请求1结果){
// 一些其他操作
请求3(function(请求2结果){
// 一些其他操作
请求4(function(请求3结果){
// 一些其他操作
请求5(function(请求4结果){
// 一些其他操作
请求6(function(请求5结果){
// 一些其他操作
...
})
})
})
})
})
})
这就是所谓的回调地狱,回调地狱带来的负面作用有以下几点:
这时,就有人思考了,能不能用一种更加友好的代码组织方式,解决异步嵌套的问题。于是 Promise 规范诞生了,promise采用链式调用,很好地解决了回调地狱的痛点。
我们使用Promise常规写法来实现上面的异步网络请求,代码如下:
new Promise(请求1)
.then(请求2(请求结果1))
.then(请求3(请求结果2))
.then(请求4(请求结果3))
.then(请求5(请求结果4))
.then(请求6(请求结果5))
.catch(处理异常(异常信息))
我们不难发现,Promise 的写法更为直观,并且能够在外层捕获异步函数的异常信息。
Promise 是异步编程的一种解决方案,比传统的解决方案【回调函数】和【事件】更合理、更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。Promise说得通俗一点就是一种写代码的方式,并且是用来写JavaScript编程中的异步代码的。
三种状态:
只有异步操作的结果才能确定当前处于哪种状态,任何其他操作都不能改变这个状态,这也是Promise(承诺)的由来。
Promise对象的状态改变,只有两种可能:
这两种情况只要发生,状态就凝固了,不会再变了,这时就称为resolved(已定型)
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消
- 如果不设置回调函数(没有捕获错误),Promise内部抛出的错误,不会反应到外部
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
我们先把Promise打印出来,会发现Promise是一个构造函数,自己身上有all、reject、resolve、race等方法,原型上有then、catch、finally等方法。
【1】Promise.prototype.constructor()
它的基本用法如下:
let promise = new Promise((resolve, reject) => {
// 在这里执行异步操作
if (/*异步操作成功*/) {
resolve(success)
} else {
reject(error)
}
})
Promise接收一个函数作为参数,函数里有resolve和reject两个参数:
- resolve方法的作用是将Promise的pending状态变为fulfilled,在异步操作成功之后调用,可以将异步返回的结果作为参数传递出去。
- reject方法的作用是将Promise的pending状态变为rejected,在异步操作失败之后调用,可以将异步返回的结果作为参数传递出去。
他们之间只能有一个被执行,不会同时被执行,因为Promise只能保持一种状态。
【2】Promise.prototype.then()
Promise实例确定后,可以用then方法分别指定fulfilled状态和rejected状态的回调函数。它的基本用法如下:
promise.then((success) => {
// 异步操作成功在这里执行
// 对应于上面的resolve(success)方法
}, (error) => {
// 异步操作失败在这里执行
// 对应于上面的reject(error)方法
})
// 还可以写成这样 (推荐使用这种写法)
promise.then((success) => {
// 异步操作成功在这里执行
// 对应于上面的resolve(success)方法
}).catch((error) => {
// 异步操作失败在这里执行
// 对应于上面的reject(error)方法
})
then(onfulfilled,onrejected)方法中有两个参数,两个参数都是函数,第一个参数执行的是resolve()方法(即异步成功后的回调方法),第二参数执行的是reject()方法(即异步失败后的回调方法)(第二个参数可选)。它返回的是一个新的Promise对象。
【3】Promise.prototype.catch()
catch方法是.then(null,onrejected)的别名,用于指定发生错误时的回调函数。作用和then中的onrejected一样,不过它还可以捕获onfulfilled抛出的错,这是onrejected所无法做到的:
function createPromise(p, arg) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (arg === 0) {
reject(p + ' fail')
} else {
resolve(p + ' ok')
}
}, 0);
})
}
createPromise('p1', 1).then((success) => {
console.log(success) // p1 ok
return createPromise('p2', 0)
}).catch((error) => {
console.log(error) // p2 fail
})
createPromise('p1', 1).then((success) => {
console.log(success) // p1 ok
return createPromise('p2', 0)
}, (error) => {
console.log(error) // Uncaught (in pomise) p2 fail
})
Promise错误具有"冒泡"的性质,如果不被捕获会一直往外抛,直到被捕获为止;而无法捕获在他们后面的Promise抛出的错。
【4】Promise.prototype.finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。该方法是 ES2018 引入的标准:
createPromise('p1', 0).then((success) => {
console.log(success)
}).catch((error) => {
console.log(error) // p1 fail
}).finally(() => {
console.log('finally') // finally
})
createPromise('p1', 1).then((success) => {
console.log(success) // p1 ok
}).catch((error) => {
console.log(error)
}).finally(() => {
console.log('finally') // finally
})
finally方法不接受任何参数,故可知它跟Promise的状态无关,不依赖于Promise的执行结果。
【5】Promise.all()
Promise.all方法接受一个数组作为参数,但每个参数必须是一个Promise实例。Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作都执行完毕后才执行回调,只要其中一个异步操作返回的状态为rejected那么Promise.all()返回的Promise即为rejected状态,此时第一个被reject的实例的返回值,会传递给Promise.all的回调函数:
function createPromise(p, arg) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (arg === 0) {
reject(p + ' fail')
} else {
resolve(p + ' ok')
}
}, 0);
})
}
// test: 两个Promise都成功
Promise.all([createPromise('p1', 1), createPromise('p2', 1)])
.then((success) => {
console.log(success) // ['p1 ok', 'p2 ok']
}).catch((error) => {
console.log(error)
})
// test: 其中一个Promise失败
Promise.all([createPromise('p1', 0), createPromise('p2', 1)])
.then((success) => {
console.log(success)
}).catch((error) => {
console.log(error) // p1 fail
})
// test: 两个Promise都失败
Promise.all([createPromise('p1', 0), createPromise('p2', 0)])
.then((success) => {
console.log(success)
}).catch((error) => {
console.log(error) // p1 fail 只打印第一个失败的异步操作信息
})
【6】Promise.race()
Promise的race方法和all方法类似,都提供了并行执行异步操作的能力。顾名思义,race就是赛跑的意思,意思就是说Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态,以下就是race的执行过程:
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})
Promise.race([p1, p2]).then((success) => {
console.log(success)
}).catch((error) => {
console.log(error) // failed
})
【7】Promise.resolve()
有时需要将现有对象转为 Promise 对象Promise.resolve()方法就起到这个作用。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
【8】Promise.reject()
Promise.reject()方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
Promise 翻译成中文为“承诺, 诺言”, 例如: 你承诺这个月挣钱了给你老婆买一个包, 那么你先去挣钱, 等挣钱了就立刻给老婆买包, 实现你的诺言, 没挣到钱就立马道歉。换成代码就是:
// 买包就是一个Promise,Promise的意思就是承诺
// 这时候老公给老婆一个承诺
// 在未来的一个月,不管挣没挣到钱,都会给老婆一个答复
let buyBag = new Promise((resolve, reject) => {
// Promise 接受两个参数
// resolve: 异步事件成功时调用(挣到钱)
// reject: 异步事件失败时调用(没挣到钱)
// 模拟挣钱概率事件
let result = function makeMoney() {
return Math.random() > 0.5 ? '挣到钱' : '没挣到钱'
}
// 下面老公给出承诺,不管挣没挣到钱,都会给老婆一个答复
if (result == '挣到钱')
resolve('我买包了')
else
reject('不好意思,我这个月没挣到钱')
})
buyBag().then(res => {
// 返回 "我买包了"
console.log(res)
}).catch(err => {
// 返回 "不好意思,我这个月没挣到钱"
console.log(err)
})
解释一下
第一段调用了Promise构造函数,第二段是调用了promise实例的.then方法
1. 构造实例
2. 调用.then