异步编程 -- Promise
Promise
为了解决回调函数经常出现的回调地狱问题,CommonJS社区提出了Promise的规范,最终在es2015中被标准化,成为语言规范
Promise用来表示一个异步任务最终是成功还是失败,像是对任务作出的承诺,许下这个承诺后,任务进入到pending状态,等待承诺的兑现,然后等承诺兑现后,状态会根据结果发生改变,可能是成功,也可能是失败,但是不再可能是pending等待状态。当状态发生发往后,会执行相应状态的回调函数。
Promise怎么使用?
这里简单用Promise模拟一下ajax
// Promise 方式的ajax
function ajax(url){
return new Promise(function(resolve,reject){
var xhr=new XMLHttpRequest()
xhr.open('GET',url)
xhr.responseType='json'
xhr.onload=function(){
if(this.status===200){
resolve(this.response)
}else{
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
// 测试
ajax('/api/foo.json')
.then(function(res){
console.log(res)
})
.catch(function(error){
console.log(error)
})
Promise常见误区
就拿上面例子举例,当我们从foo.json中获取到数据后,需要再从bar.json中继续获取数据,有时会把代码写成如下格式:
ajax('/api/foo.json')
.then(function(res){
ajax('/api/bar.json')
.then(function(res){
console.log(res)
}
})
这样写,还是形成了回调地狱,正确的写法可以是下面这样:
ajax('/api/foo.json')
.then(function(res){
// 在这里处理数据
// 。。。
// 把第二个请求进行返回
return ajax('/api/bar.json')
})
.then(function(res){
// 这样当我们还需要再进行更多的请求的时候可以接着像刚才这样写
console.log(res)
return ajax('/api/other.json')
})
.then(function(res){
console.log(res)
})
Promise 并行执行
当多个请求之间并没有因为关系时,可以让其同时请求,即并行执行,使用Promise.all和Promise.race可以完成这一要求
// Promise.all需要等全局请求都成功才能获取到结果,结果是一个数组,对应传入请求,失败一个就全部失败,进入catch
var promiseOfAll = Promise.all([
ajax('/api/foo.json'),
ajax('/api/bar.json'),
ajax('/api/other.json')
])
promiseOfAll.then(function(values){
// values是一个数组
console.log(values)
})
.catch(function(error){
console.log(error)
})
// Promise.race只等待第一个完成的请求,任何一个任务完成了,这个Promise就算完成了,全部失败才会进入catch
var promiseOfRace = Promise.race([
ajax('/api/foo.json'),
ajax('/api/bar.json'),
ajax('/api/other.json')
])
promiseOfRace.then(function(value){
console.log(value)
})
.catch(function(error){
console.log(error)
})
Promise的静态方法
// 通过resolve方法把常量转换成Promise对象
Promise.resolve('foo')
.then(function(value){
console.log(value)
})
// 通过reject方法返回一个一定是失败的Promise对象,其传入的数据就是其失败的理由
Promise.reject(new Error('rejected'))
.catch(function(error){
console.log(error)
})
Promise 执行时序
如下例子:
console.log('global start')
setTimeout(()=>{
console.log('setTimeout')
},0)
Promise.resolve()
.then(()=>{
console.log('promise')
})
.then(()=>{
console.log('Promise2')
})
.then(()=>{
console.log('Promise3')
})
console.log('end')
输出结果:
global start
end
Promise
Promise2
Promise3
setTimeout
结果分析:首先global start 和end是同步代码,会首先输出,其次是setTimeout和Promise都可以看作是宏任务,宏任务的回调可以是作为一个新的宏任务进入到队列中进行排队,也可以是作为微任务,紧跟着当前宏任务结束后立即执行,而Promise的then、catch、finally都是Promise的微任务,会在Promise之后立即执行,而setTimeout中的回调则是一个新的宏任务,会到队列的未尾重新排队,所以执行时序会靠后