基础篇主要是介绍了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对象会静默处理所有包含rejected的Promise对象操作,即不再抛出异常。
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)