ES6-Promise进阶篇

1,103 阅读4分钟

基础篇主要是介绍了Promise的基本使用方法和特点,进阶篇讲讲Promise的复杂使用方式等。

Promise连锁和Promise合成

Promise连锁

由于每个Promise实例都会返回一个新的Promise对象,并且返回的Promise对象有自己的实例方法,通过调用返回的Promise实例的方法来生成新的Promise对象,再调用其方法。这种连缀方法调用就是Promise连锁

function delayedExecute(str, callback = null) {
    setTimeout(() => {
        console.log(str)
        callback && callback()
    }, 1000)
}
delayedExecute('p1 callback', () => {
    delayedExecute('p2 callback', () => {
        delayedExecute('p3 callback', () => {
            delayedExecute('p4 callback', () => {
            })
        })
    })
})
// p1 callback (1秒后)
// p2 callback (2秒后)
// p3 callback (3秒后)
// p4 callback (4秒后)

Promise连锁来解决这个依赖回调的回调地狱问题:

function delayedExecute(str) {
    return new Promise((resolve, reject) => {
        console.log(str)
        setTimeout(resolve, 1000)
    })
}
delayedExecute('p1 excutor')
    .then(() => delayedExecute('p2 excutor'))
    .then(() => delayedExecute('p3 excutor'))
    .then(() => delayedExecute('p4 excutor'))
    
// p1 excutor (1秒后)
// p2 excutor (2秒后)
// p3 excutor (3秒后)
// p4 excutor (4秒后)

Promise图

因为一个Promise可以有多个处理程序,所以Promise连锁可以构建有向非循环图结构。每个Promise实例都是途中的一个顶点,而使用实例方法添加的处理程序则是有向顶点。因为图中的每一个节点都会等待前一个节点落定,所以图的方向就是Promise的解决或拒绝的顺序。

下面是用Promise来表示一个Promise有向图,也就是二叉树

//       A
//     /   \
//    B     C
//   / \   / \
//  D   E F   G

let A = new Promise(resolve => {
    console.log('A')
    resolve()
})
let B = A.then(() => console.log('B'))
let C = A.then(() => console.log('C'))

B.then(() => console.log('D'))
B.then(() => console.log('E'))
C.then(() => console.log('F'))
C.then(() => console.log('G'))

// A
// B
// C
// D
// E
// F
// G

Promise连锁能表示二叉树是因为Promise的处理程序是先添加到消息队列,然后才逐个执行,因此构成了二叉树的层序遍历。

Promise.all()

这个静态方法接收一个可迭代对象,并返回一个新的Promise对象,这个对象也称为合成Promise对象

语法:Promise.all([arg1, arg2, ...])

特点

  • 可迭代对象中的元素会通过Promise.resolve()转换为Promise对象。

  • 空的可迭代对象等价于Promise.resolve()

  • 不传入可迭代对象则会抛出类型错误。

  • 合成对象为resolved状态

    • 只有当可迭代对象的所有元素的Promise对象都resolved了,合成对象才会resolved
    • 合成对象的resolvedResult值是所有Promise对象的resolvedResult组成的数组,并且该数组的顺序就是可迭代对象中元素的顺序。
  • 合成对象为pending状态

    可迭代对象的元素中只要有一个Promise对象是pending状态,则合成对象的状态就是pending

  • 合成对象为rejected状态

    • 可迭代对象的元素中,只要有一个Promise对象是rejected状态,则合成对象的状态就是rejected
    • 合成对象的rejectedResult值是第一个rejected对象rejectedResult
    • 后面rejected的Promise对象不会影响已经rejected的合成对象的rejectedResult值。
    • 合成Promise对象会静默处理所有包含rejectedPromise对象操作,即不再抛出异常
    let p1 = Promise.all([
        Promise.reject(1),
        new Promise((null, reject) => {
            setTimeout(reject, 0, 2)
        })
    ])
    let p2 = p1.catch(err => {
        console.log(err) // 1
    })
    console.log(p1) // Promise <rejected>: 1
    console.log(p2) // Promise <fulfilled>: undefined
    

Promise.race()

这个静态方法接收一个可迭代对象,返回一个新Promise对象,也称为包装Promise对象,是一组集合中最先解决h或拒绝的Promise镜像

特点

  • 可迭代对象中的元素会通过Promise.resolve()转换为Promise对象。
  • 空的可迭代对象等价于 new Promise(() => {})
  • 传入不可迭代对象抛出 TypeError
  • Promise.race()顾名思义就是比赛,只有第一个得到落定状态 (pending -> resolved或pending -> rejected)Promise对象才会被包装成结果Promise。不管第一个落定状态是resolved还是rejected,后面其他的落定状态都会被忽略。如果第一个落定状态是rejected,那后面其他的rejected状态都会被静默处理,不会再抛出异常。

串行Promise合成

基于后续Promise对象使用之前Promise对象的返回值来串联Promise对象是Promise的基本功能。

function addTwo(x) {
    return x + 2
}
function addThree(x) {
    return x + 3
}
function addFive(x) {
    return x + 5
}

// 常规写法
function addTen_1(x) {
    return addFive(addThree(addTwo(x)))
}

// 使用Promise
function addTen_2(x) {
    return Promise.resolve(x)
        .then(addTwo)
        .then(addThree)
        .then(addFive)
}

// 使用Promise和Array.prototype.reduce()
function addTen_3(x) {
    return [addTwo, addThree, addFive].reduce((promise, fn) => promise.then(fn), Promise.resolve(x))
}
    
// 提炼通用函数
fucntion compose(...fns) {
    return (x) => fnx.reduce((promise, fn) => promise.then(fn), Promise.resolve(x))
}

Promise扩展

Promise取消

class CancelToken {
  constructor(cancelFn) {
    this.promise = new Promise((resolve, reject) => {
      cancelFn(() => {
        setTimeout(console.log, 0, 'delay cancelled')
        resolve()
      })
    })
  }
}

const startBtn = document.getElementById('start')
const cancelBtn = document.getElementById('cancel')

function cancellableDelayedResolve(delay) {
  setTimeout(console.log, 0, 'set delay')

  return new Promise((resolve, reject) => {
    const id = setTimeout((()=> {
      setTimeout(console.log, 0, 'delay resolve')
      resolve()
    }), delay)

    const cancelToken = new CancelToken(cancelCallback => cancelBtn.addEventListener('click', cancelCallback))
    cancelToken.promise.then(() => clearTimeout(id))
  })
}

startBtn.addEventListener('click', () => cancellableDelayedResolve)

Promise进度通知