在平时的工作中,我们不难认识到,想优雅地进行异步操作,必须要熟识一个极其重要的概念——Promise,它是取代传统函数回调,实现同步链式写法的一种解决方案。但是,对于很多像我这样的初学者来说,Promise并不是很好理解,所以,本文接下来将带你由浅入深剖析Promise的使用及原理。
异步操作的前世今生
我们先从一个异步网络请求的例子讲起:
function request(url) {
setTimeout(() => {
//判断请求是否成功
if(url === 'http...') {
//请求成功
let val = [1,2,3]
//return val ?
} else {
//请求失败
let err = "请求失败"
//return err ?
}
},5000)
}
//页面调用
request("http...")
我们封装了一个request网络请求,想要在页面中传入url调用请求,请求成功则返回一个val的数组,失败则返回"请求失败"的字符串,但是返回成功或失败的值是在异步操作中,无法通过return返回给调用页面,那怎样才能拿到请求的结果呢?
如下,我们可以传入两个回调函数successCallback,failtureCallback,在判断请求成功时调用successCallback,失败时调用failtureCallback,在页面中调用request的时候,除了传入url外,还需传入两个回调函数,在回调函数中接收返回的结果。
function request(url,successCallback,failtureCallback) {
setTimeout(() => {
//判断请求是否成功
if(url === 'http...') {
//请求成功
let val = [1,2,3]
successCallback(val)
} else {
//请求失败
let err = "请求失败"
failtureCallback(err)
}
},5000)
}
////页面调用
request("http...",(res) => {
console.log(res)
}, (err) => {
console.log(err)
})
上述操作可以成功拿到异步请求中的返回结果,但是这种方式是有弊端的
别人在使用我们封装的request请求的时候,必须看封装的源码才知道request函数怎么样获取结果
此时,Promise横空出世,对异步回调做出了规范。
function request(url) {
return new Promise((resolve,reject) => {
setTimeout(() => {
//判断请求是否成功
if(url === 'http...') {
//请求成功
let val = [1,2,3]
resolve(val)
} else {
//请求失败
let err = "请求失败"
reject(err)
}
},5000)
})
}
//页面调用
const promist_test = request('http...')
promist_test.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
Promise 对异步回调做出了规范,该规范规定,Promise接收两个回调函数(resolve和reject)作为参数,请求成功时,调用resolve,请求失败时,调用reject。同时,在页面中使用时,通过then方法接收成功的回调,catch方法接收失败的回调。
Promise 概念
ES6引入Promise,它是一个类,通过new操作符实例化,Promise是一个有状态的对象,创建Promises时,需要传入执行器(executor)函数作为参数,它共有三种状态:
- [1] pending (待定) : 初始状态,执行executor中的代码时,处于该状态
- [2] fulfilled/resolved (兑现) :执行resolve时,处于该状态
- [3] rejected (拒绝) : 执行reject时,处于该状态
Promise的对象方法
对象方法在通过new关键字创建出的对象上使用
then
then是定义在Promise原型对象上的方法,它最多可以接收两个函数作为参数
语法:Promise.then(onFulfilled,onRejected)
onFulfilled 接收fulfilled状态返回的结果
onRejected 接收rejected状态返回的结果
const promise = new Promise((resolve,reject) => {
resolve(123)
// reject("errrr")
})
promise.then(res => {
console.log(res)
},err => {
console.log(err)
})
上述栗子中,都是只调用了一次then方法,那么,当同一一个Promise被多次调用then方法时,会发生什么呢?
const promise = new Promise((resolve,reject) => {
resolve(123)
})
promise.then((res) => {
console.log('res1',res)
})
promise.then((res) => {
console.log('res2',res)
})
promise.then((res) => {
console.log('res3',res)
})
打印结果如下:
res1 123
res2 123
res3 123
不难看出,当一个Promise的resolve被调用时,所有then方法传入的回调都会被执行
then的链式调用
const promise = new Promise((resolve,reject) => {
resolve(2)
})
promise.then(res => {
return(res*2)
}).then(res => {
return(res+2)
}).then(res => {
console.log(res) //6
})
那么then方法为什么可以这样连续调用呢?下面随我们来揭晓答案:
then方法它本身有返回值,并且它的返回值是Promise,如果then中return一个普通值,那么它会自动给这个值包裹一层Promise。哇偶,原来真相竟是这样!
那么问题来了,如果then中又返回一个Promise呢?
const test = new Promise((resolve,reject) => {
setTimeout(() => {
resolve("状态由我决定")
},5000)
})
const promise = new Promise((resolve,reject) => {
resolve(2)
})
promise.then(res => {
return test
}).then(res => {
console.log(res) //状态由我决定
})
其实在then方法中return一个Promise时,还是会自动给它包裹一层Promise,但是此时Promise的状态会进行移交,由return的Promise决定。
到这大家应该很容易就能想到还有第三种情况,return一个实现了then方法的对象,同样,它还是自动被包裹一层Promise,但是会进行状态移交,状态由return的这个对象决定,下面简单进行下代码演示:
const obj = {
then: function(resolve,reject) {
resolve("状态由我决定")
}
}
const promise = new Promise((resolve,reject) => {
resolve(2)
})
promise.then(res => {
return obj
}).then(res => {
console.log(res) //状态由我决定
})
catch
通过上述对then方法的介绍,我们知道then的第二个参数可以接收reject状态返回的结果,即onRejected 接收rejected状态返回的结果,但是then的这种写法对于代码的阅读性不太友好,所以ES6增加了catch方法来处理reject状态的结果。
const promise = new Promise((resolve,reject) => {
reject("优先捕获")
//resolve("OK")
})
promise.then(res => {
return new Promise((resolve,reject) => {
reject("也能捕获我")
})
}).catch(err => {
console.log(err) //优先捕获
})
catch不仅能捕获promise中抛出的异常,也能处理then中抛出的异常,但是会优先捕获promise中的异常,当promise为resolve时,才会捕获then中的异常。 catch本身也是有返回值的,它返回一个Promise,catch中return的值也会自动包裹一层Promise,所以它也是可以链式调用的,当然这里return的值和then一样也分三种情况,此处只列举普通值,就不一一赘述了。
const promise = new Promise((resolve,reject) => {
reject("优先捕获")
})
promise.then(res => {
return new Promise((resolve,reject) => {
reject("也能捕获我")
})
}).catch(err => {
console.log(err) // 优先捕获
return "catch"
}).then(res => {
console.log(res) // catch
})
finally
finally方法不接受任何参数,无论promise的状态是fulfilled还是rejected,finally的代码都会执行
const promise = new Promise((resolve,reject) => {
resolve("123")
})
promise.then(res => {
console.log(res) //123
}).finally(() => {
console.log("我都会被执行") //我都会被执行
})
Promise的类方法
类方法可以直接通过点操作符直接调用,比如:Promise.resolve()
resolve
resolve作为类方法的调用
const test = Promise.resolve("resolve作为类方法调用")
test.then(res => {
console.log(res) //resolve作为类方法调用
})
在Promise中resolve返回的是成功的结果,接下来,我们深入了解下resolve返回不同参数的情况
- [1] resolve()传入 普通值/object,状态由pending--→fulfilled,返回传入的普通值/object
const test = new Promise((resolve,reject) => {
resolve({name:"张三",age:18})
})
test.then(res => {
console.log(res) //{ name: '张三', age: 18 }
}) .catch(err => {
console.log(err)
})
- [2] resolve()传入 Promise,当前test的状态由传入的promise来决定,返回传入的promise的状态中的值
const promise = new Promise((resolve,reject) => {
resolve('状态由我决定')
// reject("错误")
})
const test = new Promise((resolve,reject) => {
resolve(promise)
})
test.then(res => {
console.log(res) //状态由我决定
}) .catch(err => {
console.log(err)
})
- [3] resolve()传入 一个实现了then方法的对象,当前test的状态由传入的obj中then方法的状态决定,返回then方法的状态中的值
const test = new Promise((resolve,reject) => {
const obj = {
then: function(resolve,reject) {
resolve("在这儿")
// reject('err')
}
}
resolve(obj)
})
test.then(res => {
console.log(res) //在这儿
}) .catch(err => {
console.log(err)
})
reject
reject作为类方法调用
const test = Promise.reject("reject类方法调用")
test.catch(err => {
console.log(err) //reject类方法调用
})
reject传入什么就返回什么,也就是说假如你传入的值是Promise,那么它将原封不动的将这个传入的Promise返回,不会做状态的移交。
all
Promise.all分为两种情况
一种是传入的所有promise都是fulfilled状态:传入的所有Promise状态都变为fulfilled后,all方法才能拿到结果。
const test1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve(111)
},1000)
})
const test2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve(222)
},2000)
})
const test3 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve(333)
},3000)
})
Promise.all([test1,test2,test3]).then(res => {
console.log(res) //[ 111, 222, 333 ]
})
另一种是传入的Promise存在rejected状态:传入的Promise只要有一个状态是rejected,那么整个Promise都是rejected。
const test1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve(111)
},1000)
})
const test2 = new Promise((resolve,reject) => {
setTimeout(() => {
reject(222)
},2000)
})
const test3 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve(333)
},3000)
})
Promise.all([test1,test2,test3]).catch(err => {
console.log(err) //222
})
all方法有一个不太友好的地方就是,如果其中有一个Promise处于rejected,那么其他处于fulfilled状态的结果会获取不到。
allSettled
allSettled 无论传入的Promise处于什么状态,他们的状态最终都会被返回。使用allSettled的Promise的状态一定是fulfilled
const test1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve(111)
},1000)
})
const test2 = new Promise((resolve,reject) => {
setTimeout(() => {
reject(222)
},2000)
})
const test3 = new Promise((resolve,reject) => {
setTimeout(() => {
reject(333)
},3000)
})
Promise.allSettled([test1,test2,test3]).then(res => {
console.log(res)
})
打印结果:
[
{ status: 'fulfilled', value: 111 },
{ status: 'rejected', reason: 222 },
{ status: 'rejected', reason: 333 }
]
race
有一个Promise状态变成fulfilled/rejected,就结束;对于返回值来说,谁先fulfilled/rejected,就返回谁的结果。
const test1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve(111)
},1000)
})
const test2 = new Promise((resolve,reject) => {
setTimeout(() => {
reject(222)
},2000)
})
const test3 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve(333)
},3000)
})
Promise.race([test1,test2,test3]).then(res => {
console.log(res) // 111
})
any
对于race来说,无论是fulfilled还是rejected,谁先改变状态,就返回谁的值。但是any会等待状态变为fulfilled,有Promise变为fulfilled,就结束,返回这个fulfilled的结果;如果传入的Promise全是rejected,那么会返回一个错误
const test1 = new Promise((resolve,reject) => {
setTimeout(() => {
reject(111)
},1000)
})
const test2 = new Promise((resolve,reject) => {
setTimeout(() => {
reject(222)
},2000)
})
const test3 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve(333)
},3000)
})
Promise.any([test1,test2,test3]).then(res => {
console.log(res) // 333
})
传入的Promise全是rejected
const test1 = new Promise((resolve,reject) => {
setTimeout(() => {
reject('err1')
},1000)
})
const test2 = new Promise((resolve,reject) => {
setTimeout(() => {
reject('err2')
},2000)
})
const test3 = new Promise((resolve,reject) => {
setTimeout(() => {
reject('err3')
},3000)
})
Promise.any([test1,test2,test3]).then(res => {
console.log(res)
}).catch(err => {
console.log(err.errors) //['err1', 'err2', 'err3']
})