新手不太注意的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