Promise

发布时间 2023-07-01 14:04:28作者: wdszh

Promise

前端的异步运行机制

JavaScript作为单线程运行于浏览器之中,这是每本JavaScript教科书中都会被提到的。同时出于对UI线程操作的安全性考虑,JavaScript和UI线程也处于同一个线程中。因此对于耗时的操作,将会阻塞UI的响应。为了更好的UI体验,应该尽量的避免JavaScript中执行较长耗时的操作(如大量for循环的对象diff等)或者是长时间I/O阻塞的任务。所以在浏览器中的大多数任务都是异步(无阻塞)执行的,例如:鼠标点击事件、窗口大小拖拉事件、定时器触发事件、Ajax完成回调事件等。当每一个异步事件完成时,它都将被放入一个叫做“浏览器事件队列”的事件池中去。而这些被放在事件池中的任务,将会被JavaScript引擎单线程的一个一个的处理,当在此次处理中再次遇见的异步任务,它们也会被放到事件池中去,等待下一次的tick被处理。另外在HTML5中引入了新的组件-Web Worker,它可以在JavaScript线程以外执行这些任务,而不阻塞当前UI线程。

浏览器中的事件循环模型如下图所示:

browser-event-base

MainLoop-单线程

由于浏览器的这种内部事件循环机制,所以JavaScript一直以callback回调的方式来处理事件任务。因此无所避免的对于多个的JavaScript异步任务的处理,将会遇见callback hell(回调地狱),使得这类代码及其不可读和难易维护。

asyncTask1(data, function (data1){
    asyncTask2(data1, function (data2){
        asyncTask3(data2, function (data3){
                // .... 魔鬼式的金字塔还在继续
        })
    })
})

为了解决这种回调地狱问题Promise就诞生了

Promise对象

Promise/A+规范

  • Promise对象有三种状态: Pending– Promise对象的初始状态,等待任务的完成或者被拒绝;Fulfilled– 任务执行完成并且成功的状态;Rejected– 任务执行完成并且失败的状态
  • Promise的状态只可能从Pending状态转到Fulfilled状态或者Rejected状态,而且不能逆向转换,同时Fulfilled状态和Rejected状态也不能相互转换
  • Promise对象必须实现then方法,thenpromise规范的核心,而且then方法也必须返回一个Promise对象,同一个Promise对象可以注册多个then方法,并且回调的执行顺序跟它们的注册顺序一致
  • then方法接受两个回调函数,它们分别为:成功时的回调和失败时的回调;并且它们分别在:Promise由Pending状态转换到Fulfilled状态时被调用和在Promise由Pending状态转换到Rejected状态时被调用

可以表示为下图

流程图

错误处理

myPromise
  .then(handleFulfilledA)
  .then(handleFulfilledB)
  .then(handleFulfilledC)
   // 如果以上那一个then出现错误,则会跳过其它的then处理,直接跳到catch中进行处理
  .catch(handleRejectedAny)

多个异步任务的串行处理

我们可以将上面回调地狱的代码转换成如下

asyncTask1(data)
    .then(data1 =>{
        return asyncTask2(data1);
    })
    .then(data2 => {
       return asyncTask3(data2);
    })
    // 仍然可以继续then方法

Promise将原来回调地狱中的回调函数,从横向式增加巧妙的变为了纵向增长。以链式的风格,纵向的书写,使得代码更加的可读和易于维护。

对于解决这类异步任务的方式,在ES7中将会引入async、await两个关键字,以同步的方式来书写异步的任务,它被誉为JavaScript异步处理的终极方案。这两个关键字是ES6标准中生成器(generator)和Promise的组合新语法,内置generator的执行器的一种方式。当然async、await的讲解并不会在本文中展开,有兴趣的读者可以参见MDN资料

多个异步任务的并行处理

在有些场景下,我们所要处理的多个异步任务之间并没有像上例中的那么强的依赖关系,只需要在这一系列的异步任务全部完成的时候执行一些特定逻辑

myPromise.all([
    http.get('/demo1'),
    http.get('/demo2'),
    http.get('/demo3')
])
.then(function(results){
    console.log('result 1', results[0])
    console.log('result 2', results[1])
    console.log('result 3', results[2])
})

获取最先完成的

多个任务同时进行,只获取最先返回结果

myPromise.race([
    http.get('/demo1'),
    http.get('/demo2'),
    http.get('/demo3')
])
.then(result => {
    console.log('result = ', results)
})

async/await异步函数

这其实是一个语法糖

// 定义了一个返回Promise的函数
function add(a:number, b:number): Promise<number>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            return resolve(a+b)
        }, 2000)
    })
}


async function call(){
    try{
        // 使用await 可以直接获取Promise的返回值类似相当于写在then中
        const a = await add(2,4)
        console.log(a)
    }catch(err){
        // 异常处理
        console.log(err)
    }
}


async function call(){
    // await 可以直接加在Promise前面,变量就能接收到them里面的返回值了
    const [a,b] = await Promise.all([add(1,2), add(3,6)])
    console.log(a,b)
}

call()