阅读 70

JavaScript异步编程--Promise

Promise

一种更优的异步编程统一方案,但是直接使用传统回调方式去完成复杂的异步流程会造成大量的回调问题(回调地狱),CommonJS社区提出了Promise的规范,目的就是为异步编程提供更合理更规范的统一解决方案,在ES2015中被标准化,成为语言规范。

Promise就是一个对象,用来表示一个异步任务最终结束之后究竟是成功还是失败,就像是内部对外界做出了一个承诺,一开始这个承诺是待定的状态(pending),最终有可能是成功(Fulfilled)对应的回调函数为onFulfilled,也可能是失败(Rejected)对应的回调函数为onRejected。

示例:

    // Promise接收一个函数,函数有两个参数(都是函数),resolve和reject
    const promise = new Promise(function (resolve, reject) {
        // resolve函数的作用就是把Promise的状态修改为Fulfilled
        // 成功的结果就是通过这个resolve传递出去
        resolve(100)
        
        // reject函数的作用就是把Promise的状态修改为Rejected
        // 失败的结果通过这个reject传递出去
        // reject传递的参数一般是一个错误对象,描述错误的原因
        reject(new Error('promise rejected'))
    })
    
    promise.then(function(value) {
        // 当返回resolve时会调用这个回调函数
        console.log('resolve', value) // => 100
    }, function(err) {
        // 当返回reject时会调用这个回调函数
        console.log('rejected', err) // => Error对象
    })
复制代码

Promise链式调用

  • Promise对象的then方法会返回一个全新的Promise对象
  • 后面的then方法就是在为上一个then返回的Promise注册回调
  • 前面then方法中回调函数的返回值会作为后面then方法回调的参数
  • 如果回调中返回的是Promise,那后面then方法的回调会等待它的结束

Promise的异常处理

onRejected是为Promise中的异常做一些处理,如:Promise主动调用reject方法、调用一个不存的方法、主动抛出一个Error异常。

通过Promise的catch方法捕获异常

    promise.then(function(value) {
        // 当返回resolve时会调用这个回调函数
        console.log('resolve', value) // => 100
    })
    .catch(function(err) { // catch方法其实就是reject方法的一个别名
        // 当返回reject时会调用这个回调函数
        console.log('rejected', err) // => Error对象
    })
复制代码

catch不then方法第二个参数捕获异常的不同点:

  • catch捕获的是上一个then方法出现的异常
  • then方法第二个参数捕获的是当前Promise出现的异常

如果在Promise链条中发生异常,这个异常会被一直往后传递,直到被捕获,所以catch方法更像是给整个链条注册的异常捕获。

Promise的静态方法

resolve方法直接将值返回

示例:

    Promise.resolve('foo')
        .then(function(value) {
            console.log(value) // => foo
        })
    
    // 上边得到的value就和下边这种一样
    new Promise(function(resolve, reject) {
        resolve('foo')
    })
复制代码

使用Promise对象的静态方法resolve包装一个Promise,得到的是原本被包装的Promise 示例:

    let promise = ajax('/api/users.json')
    let promise2 = Promise.resolve(promise)
    console.log(promise === promise2) // => true
复制代码

Promise的resolve方法返回一个带有then方法的对象,这个对象也具有onFulfilled方法时,我们也可以通过then方法拿到返回的值 示例:

    Promise.resolve({
        then: function(onFulfilled, onRejected) {
            onFulfilled('foo')
        }
    })
    .then(function(value) {
        console.log(value) // => foo
    })
复制代码

与resolve静态方法对应的是reject静态方法,reject静态方法返回就是错误信息,使用上和resolve类似,如:Promise.reject(new Error('xxx'))

Promise并行执行

Promise.all

当有多个异步任务需要同时处理时,在之前是通过定义一个计数器的形式来判断所有异步任务是否都执行完成。在Promise中现在给我们提供了一个可以同时处理多个异步任务的方法:all。

示例:

    let promise = Promise.all([
        ajax('/api/users.json'),
        ajax('/api/posts.json')
    ])
    
    // all方法会当所有异步任务都执行完成之后返回一个Promise对象
    // 只有当所有异步任务都成功时才返回resolve,否则返回reject
    peomise.then(function(value) {
        // value传递的为一个数组,数组中包括所有异步任务的执行结果
        console.log(value) // => [xx, xx]
    })
复制代码

Promise.race

race方法也同样可以将多个Promise对象组合为一个全新的Promise对象,与all方法不同的是all方法等待所有异步任务结束之后结束;race方法是只会等待第一个异步任务结束后结束,也就是只要其中任何一个异步任务完成之后race方法就会结束。

示例:

    const request = ajax('/api/posts.json')
    const timer = new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error('timeout')), 500)
    })
    
    Promise.race([
        request,
        timer
    ])
    .then(value => {
        // 如果500ms以内request方法成功则执行成功的回调
        console.log(value)
    })
    .catch(err => {
        // 如果500ms以内request方法没有成功,则执行失败的回调
        console.log(err)
    })
复制代码

Promise执行时序

回调队列中任务通常被称作宏任务,宏任务执行过程中可能会添加一些额外的需求,新添加的需求可以选择作为一个新的宏任务进入到队列中排队,例如setTimeout会在宏任务中排队,也可以作为一个微任务,直接在当前任务结束之后立即执行。 Promise的回调作为微任务执行,在本轮调用结束的末尾立即执行。 微任务:为了提高整体的响应能力 目前绝大多数异步调用都是作为宏任务执行,而Promise对象、MutationObserver、node中的process.nextTick是作为微任务执行

示例:

    console.log('begin')
    
    // 作为宏任务执行
    setTimeout(() => {
        console.log('timer')
    }, 0)
    
    // 作为微任务执行
    Promise.resolve()
        .then(() => {
            console.log('promise')
        })
        .then(() => {
            console.log('promise2')
        })
        
    console.log('end')
    
    // => begin => end => promise => promise2 => timer
复制代码