基础篇主要是介绍了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)