1、为什么要使用Promise
防止回调嵌套层数过多导致的回调地狱 ,用promise来解决异步问题
例如:
setTimeout(function(){
//第⼀秒后执⾏的逻辑
console.log('第⼀秒之后发⽣的事情')
setTimeout(function(){
//第⼆秒后执⾏的逻辑
console.log('第⼆秒之后发⽣的事情')
setTimeout(function(){
//第三秒后执⾏的逻辑
console.log('第三秒之后发⽣的事情')
},1000)
},1000)
},1000)
//效果:每隔1秒后输出,内容过多会导致“回调地狱”
2、Promise如何解决异步控制问题
Promise通过链式调用的结构,将原本回调嵌套的异步处理流程变成了.then( ).then( )... 的链式结构。这样虽然仍然离不开回调函数,但是在编程阅读上有了很大改进,更易于维护。
//使⽤Promise拆解的setTimeout流程控制
var p = new Promise(function(resolve){
setTimeout(function(){
resolve()
},1000)
})
p.then(function(){
//第⼀秒后执⾏的逻辑
console.log('第⼀秒之后发⽣的事情')
return new Promise(function(resolve){
setTimeout(function(){
resolve()
},1000)
})
}).then(function(){
//第⼆秒后执⾏的逻辑
console.log('第⼆秒之后发⽣的事情')
return new Promise(function(resolve){
setTimeout(function(){
resolve()
},1000)
})
}).then(function(){
//第三秒后执⾏的逻辑
console.log('第三秒之后发⽣的事情')
})
3、回调函数
把函数当作变量传递给另一个函数。回调函数本身是同步代码
//把fn当作函数对象那么就可以在test函数中使⽤()执⾏他
function test(fn){
fn()
}
//那么运⾏test的时候fn也会随着执⾏,所以test中传⼊的匿名函数就会运⾏
test(function(){
...
})
js中的回调函数默认是同步结构,但由于js单线程异步模型的规则,要想编写异步的代码,必须使用回调嵌套的形式才能实现。所以:回调函数结构不一定是异步代码,但异步代码一定是回调结构
4、Promise结构
resolve() 和 reject () 只能执行一个,两个都写则后一个不执行;两个都不写则没有输出finally也不会执行
//实例化⼀个Promise对象
var p = new Promise(function(resolve,reject){
resolve() //执行then()
reject() //执行catch()
})
//通过链式调⽤控制流程
p.then(function(){
console.log('then执⾏')
}).catch(function(){
console.log('catch执⾏')
}).finally(function(){
console.log('finally执⾏')
})
5、Promise链式调用
运行下边代码看结果
//通过⼀个超⻓的链式调⽤我们学习⼀下链式调⽤的注意事项
var p = new Promise(function(resolve,reject){
resolve('我是Promise的值')
})
console.log(p)
p.then(function(res){
//该res的结果是resolve传递的参数
console.log(res)
}).then(function(res){
//该res的结果是undefined
console.log(res)
return '123'
}).then(function(res){
//该res的结果是123
console.log(res)
return new Promise(function(resolve){
resolve(456)
})
}).then(function(res){
//该res的结果是456
console.log(res)
return '我是直接返回的结果'
}).then()
.then('我是字符串')
.then(function(res){
//该res的结果是“我是直接返回的结果”
console.log(res)
})
根据上面现象得出链式调用的基本形式(参数):
- 只要有
then()并且触发了resolve,整个链条就会执行到结尾,这个过程中的第一个回调函数的参数就是resolve传递的参数 - 后续每个then( )内函数都可以使用 return 返回一个结果,如果没有返回的话,下一个then中回调函数的参数就是undefined
- 返回结果如果是普通变量,那么这个值就是 下一个then中接受的参数
- 如果返回的是一个Promise对象,那么它的resolve() 就是下一个then中接受的参数、
- 如果then中传入的不是函数,或者未传值,Promise链条也不会中断then的链式调用,并且在这之前最后一次的返回结果,会进入下一个离它最近的then中作为参数传递。
6、中断链式调用
有两种形式可以中断.then的链式调用
- 方式一:抛出一个异常
throw('我是中断的原因:抛出异常') - 方式二:返回一个reject的 promise对象
var p = new Promise(function(resolve,reject){
resolve('我是Promise的值')
})
console.log(p)
p.then(function(res){
console.log(res)
}).then(function(res){
//有两种⽅式中断Promise
//throw('我是中断的原因:抛出异常')
return Promise.reject('我是中断的原因')
}).then(function(res){
console.log(res)
}).then(function(res){
console.log(res)
}).catch(function(err){
console.log(err)
})
7、小结:
根据以上promise运行时的规则就能解释的通,为什么最初通过Promise控制setTimeout每秒执行一次的代码可以实现。 因为:当我们使用.then()进行链式调用时,可以利用返回一个新的promise对象来执行下一次then函数,而下一次then函数的执行,必须等待其内部resolve的调用。这样我们再new Promise时,放入setTimeout来进行延时,保证1秒之后让状态变更,就不用编写回调嵌套来实现连续的执行异步流程了。
8、Promise常用api
Promise.all()
使用案例: 需要同时调用三个服务端接口接口,且保证三个数据的接口全部返回数据后才能渲染页面。接口a耗时1s, 接口b耗时0.8s,接口c耗时1.4s。
如果.then().then().then()链式调用需要花费的时间是 3.2s 。这种累加显然增加了接口调用的耗时,所以promise 提供了一个all()方法 :
Promise.all([promise对象,promise对象,...]).then(回调函数)
等最慢的接口返回数据一起得到所有接口的数据,这个耗时只会按最慢的接口消耗 1.4 s
let p1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('第⼀个promise执⾏完毕')
},1000)
})
let p2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('第⼆个promise执⾏完毕')
},800)
})
let p3 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('第三个promise执⾏完毕')
},1400)
})
Promise.all([p1,p3,p2]).then(res => {
console.log(res)
}).catch(function(err){
console.log(err)
})
//结果:res返回一个数组: ['第⼀个promise执⾏完毕','第二个promise执⾏完毕','第三个promise执⾏完毕']
多个promise任务,保证处理的这些所有promise对象的状态全部变成为fulfilled之后才会出发all的.then函数来保证将放置在all中的所有任务的结果返回
Promise.race()
使用案例: 播放某个视频时,有多个播放源,为了保证用户获得较迟的延时,要求使用耗时最短的播放源作为视频的默认播放源。
let p1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('第⼀个promise执⾏完毕')
},5000)
})
let p2 = new Promise((resolve,reject) => {
setTimeout(() => {
reject('第⼆个promise执⾏完毕')
},2000)
})
let p3 = new Promise(resolve => {
setTimeout(() => {
resolve('第三个promise执⾏完毕')
},3000)
})
Promise.race([p1,p3,p2]).then(res => {
console.log(res)
}).catch(function(err){
console.error(err)
})
//结果:res返回 第⼆个promise执⾏完毕
promise.race()相当于将传⼊的所有任务 进⾏了⼀个竞争,他们之间最先将状态变成fulfilled的那⼀个任务就会直接的触发race的.then函数并且将他的值返回,主要⽤于多个任务之间竞争时使⽤
9、关于Async 和 Await
async 修饰的函数会被解释成promise对象。
运行以下程序查看结果
async function test(){
console.log(3)
var a = await 4
console.log(a)
}
console.log(1)
test()
console.log(2)
//结果: 1 3 2 4
为什么会出现这样的情况呢,其实将async翻译成promise结构就很容易理解了
console.log(1)
new Promise(function(resolve,reject)=>{
console.log(3)
resolve(4)
}).then(function(a){
console.log(a)
})
console.log(2)
综上可以看出,async最大的特点,就是第一个await右边和上边的代码都是同步区域,相当于new Promise的回调;第一个await的下边就成了.then中异步执行的代码,await的左边是resolve传递给 then的参数。