新手不太注意的promise细节和eventloop
基础用法
const p=new Promise((resolve,reject)=>{
if(...){
reject(new Error())
}
resolve(100)
})
p.then((val)=>{
console.log(val) // 100
},(err)=>{
console.log('rejected',err)
})
ajax如何封装为promise
function ajax(url){
return new Promise((res,rej)=>{
const xhr=new XMLHttpRequest()
xhr.open('GET',url)
xhr.onload=function(){
if(this.status===200){
resolve(this.response)
}else{
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
ajax('api/a/b.json').then((res)=>{
console.log(res)
},(err)=>{
console.log(err)
})
promise链式调用
每一个then方法都是在为上一个then返回状态明确的回调。Promise的then方法会返回一个全新的promise对象。then方法里,如果返回一个确定的简单值如’aa’,可以视为return Promise.resolve('aa')
。如果没有返回值则视为返回Promise.resolve(undefined)
。后面的then方法就是在为上一个then返回的Promise注册回调,前面的then方法中回调函数的返回值会作为后面的then方法回调的参数,如果回调中返回的是promise对象,后面的then方法的回调会等待它的决议。
promise异常处理
then方法中的异常捕获和promise.catch有很大不同,看下面代码
const p=()=>=new Promise(....)
p() //1
.then(()=>{},(err)=>{
// 只能捕获到1处的异常
})
p()//1
.then(()=>{}) //2
.catch(err=>{
// 1、2处的异常都可以捕获
})
promise上的任何异常都会向后传递直至捕获。
全局捕获未定义异常
可以注册unhandledrejection事件来捕获未被定义的异常
window.addEventListener('unhandledrejection',event=>{
const {reason,promise}=event
console.log(reason,promise)
// reason失败原因,promise失败对象
event.preventDefault();
},false)
同理,对于node环境:
process.addEventListener('unhandledrejection',(reason,promise)=>{
console.log(reason,promise)
// reason失败原因,promise失败对象
})
Promise静态方法
Promise.resolve
传入promise对象
const promise1=ajax('/api/....')
const promise2=Promise.resolve(promise)
promise1===promise2 // true
传入thenable对象
Promise.resolve({
then:(onFullfilled,onRejected)=>{
onFullfilled('foo')
}
})
.then(val=>{
console.log(val) // foo
})
Promise.reject
Promise.all(Array)
Promise.all(Array)等待所有的任务都成功结束才会成功结束,只要有一个任务失败,这个promise就会以失败结束
Promise.race(Array)
竞争态,只会返回第一个结束的任务。
promise执行时序(宏任务与微任务)
宏任务可以选择作为一个新的宏任务进到队列中排队,也可以作为当前任务的微任务,直接在当前任务结束过后立即执行,而不是到整个消息队列中重新排队。目前大多数异步调用都是作为宏任务执行
宏任务:setTimeout、setInterval、requestAnimationFrame
微任务:promise回调,MutationObserver,process.nextTick
一道面试题:
setTimeout(_ => console.log(4))
new Promise(resolve => {
resolve()
console.log(1)
}).then(_ => {
console.log(3)
})
console.log(2)
打印 1、2、3、4。4先进入消息队列(宏任务),promise实例化是同步代码,所以按照顺序先执行1,promise的回调3是异步放入消息队列(微任务),之后同步下来打印2.eventloop启动,先执行微任务,打印3,执行宏任务打印4.
eventloop代码表示
const macroTaskList = [
['task1'],
['task2', 'task3'],
['task4'],
]
for (let macroIndex = 0; macroIndex < macroTaskList.length; macroIndex++) {
const microTaskList = macroTaskList[macroIndex]
for (let microIndex = 0; microIndex < microTaskList.length; microIndex++) {
const microTask = microTaskList[microIndex]
// 添加一个微任务
if (microIndex === 1) microTaskList.push('special micro task')
// 执行任务
console.log(microTask)
}
// 添加一个宏任务
if (macroIndex === 2) macroTaskList.push(['special macro task'])
}
// > task1
// > task2
// > task3
// > special micro task
// > task4
// > special macro task