共计 24520 个字符,预计需要花费 62 分钟才能阅读完成。
关注前端小讴,浏览更多原创技术文章
期约
- 期约是对尚不存在后果的一个替身,是一种异步程序执行的机制
相干代码 →
Promises/A+ 标准
- ES6 新增了
Promise
类型,其成为主导性的异步编程机制,所有古代浏览器都反对期约
期约根底
Promise
类型通过new
操作符实例化,需传入 执行器(executor)函数 作为参数
// let p = new Promise() // TypeError: Promise resolver undefined is not a function,必须提供执行器函数作为参数
let p = new Promise(() => {})
setTimeout(console.log, 0, p) // Promise {<pending>}
期约状态机
-
期约是一个有状态的对象:
- 待定
pending
示意 尚未开始或正在执行 。最初始状态,能够落定为兑现或回绝状态, 兑现后不可逆 - 兑现
fullfilled
(或解决resolved
)示意胜利实现 - 回绝
rejected
示意没有胜利实现
- 待定
- 期约的状态是 公有 的,其将异步行为 封装 起来 隔离 内部代码,不能 被内部 JS 代码读取或批改
解决值、回绝理由及期约用例
-
期约的状态机能够提供很有用的信息,假如其向服务器发送一个 HTTP 申请:
- 返回 200-299 范畴内的状态码, 可让期约状态变为“兑现”,期约 外部 收到 公有 的
JSON
字符串,默认值为 undefined - 返回不在 200-299 范畴内的状态码,会把期约状态切换为“回绝”,期约 外部 收到 公有 的
Error
对象(蕴含谬误音讯),默认值为 undefined
- 返回 200-299 范畴内的状态码, 可让期约状态变为“兑现”,期约 外部 收到 公有 的
通过执行函数管制期约状态
- 期约的状态是 公有 的,只能在 执行器函数 中实现 外部操作
-
执行器函数负责 初始化期约异步行为 和管制状态转换:
- 通过
resolve()
和reject()
两个函数参数管制状态转换 resolve()
会把状态切换为 兑换 ,reject()
会把状态切换为 回绝 并抛出谬误
- 通过
let p1 = new Promise((resolve, reject) => resolve())
setTimeout(console.log, 0, p1) // Promise {<fulfilled>: undefined}
let p2 = new Promise((resolve, reject) => reject())
setTimeout(console.log, 0, p2) // Promise {<rejected>: undefined}
// Uncaught (in promise)
- 执行器函数是期约的初始化程序,其是 同步 执行的
new Promise(() => setTimeout(console.log, 0, 'executor'))
setTimeout(console.log, 0, 'promise initialized')
/*
'executor',先打印
'promise initialized',后打印
*/
- 可增加
setTimeout
推延执行器函数的切换状态
let p3 = new Promise((resolve, reject) => {setTimeout(resolve, 1000)
})
setTimeout(console.log, 0, p3) // Promise {<pending>},打印实例 p3 时,还不会执行外部回调
resolve()
和reject()
无论哪个被调用,状态转换都 不可撤销 ,持续批改状态会 静默失败
let p4 = new Promise((resolve, reject) => {resolve()
reject() // 静默失败})
setTimeout(console.log, 0, p4) // Promise {<fulfilled>: undefined}
- 为防止期约卡在待定状态,可 增加定时退出 性能,设置若干时长后无论如何都回绝期约的回调
let p5 = new Promise((resolve, reject) => {setTimeout(reject, 10000) // 10 秒后调用 reject()})
setTimeout(console.log, 0, p5) // Promise {<pending>},10 秒内,不调用 resolve()
setTimeout(console.log, 11000, p5) // Promise {<rejected>: undefined},10 秒外,调用 reject()
// Uncaught (in promise)
Promise.resolve()
- 调用
Promise.resolve()
办法,能够 实例化 一个 解决的 期约
let p6 = new Promise((resolve, reject) => {resolve()
})
console.log(p6) // Promise {<fulfilled>: undefined}
let p7 = Promise.resolve()
console.log(p7) // Promise {<fulfilled>: undefined}
- 传给
Promise.resolve()
的第一个参数 为解决的期约的值
setTimeout(console.log, 0, Promise.resolve()) // Promise {<fulfilled>: undefined}
setTimeout(console.log, 0, Promise.resolve(3)) // Promise {<fulfilled>: 3}
setTimeout(console.log, 0, Promise.resolve(4, 5, 6)) // Promise {<fulfilled>: 4},只取首个参数
Promise.resolve()
是一个 幂等 办法,如果传入的 参数是一个期约 ,其行为相似于一个 空包装
let p8 = Promise.resolve(7)
setTimeout(console.log, 0, Promise.resolve(p8)) // Promise {7}
setTimeout(console.log, 0, p8 === Promise.resolve(p8)) // true
setTimeout(console.log, 0, p8 === Promise.resolve(Promise.resolve(p8))) // true
- 该幂等性会 保留 传入期约的 状态
let p9 = new Promise(() => {}) // 待定状态
setTimeout(console.log, 0, p9) // Promise {<pending>}
setTimeout(console.log, 0, Promise.resolve(p9)) // Promise {<pending>}
setTimeout(console.log, 0, Promise.resolve(Promise.resolve(p9))) // Promise {<pending>}
- 该办法可能包装 任何非期约值 (包含谬误对象),并将其 转换为解决的期约,因而可能导致不合乎预期的行为
let p10 = Promise.resolve(new Error('foo'))
setTimeout(console.log, 0, p10) // Promise {<fulfilled>: Error: foo
Promise.reject()
-
与
Promise.resolve()
类似,Promise.reject()
能够 实例化 一个 回绝的 期约并 抛出一个异步谬误- 该谬误 不能 通过
try/catch
捕捉,只能通过回绝处理程序捕捉
- 该谬误 不能 通过
let p11 = new Promise((resolve, reject) => {reject()
})
console.log(p11) // Promise {<rejected>: undefined}
// Uncaught (in promise)
let p12 = Promise.reject()
console.log(p12) // Promise {<rejected>: undefined}
// Uncaught (in promise)
- 传给
Promise.resolve()
的第一个参数 为回绝的期约的理由,该参数也会 传给后续的回绝处理程序
let p13 = Promise.reject(3)
setTimeout(console.log, 0, p13) // Promise {<rejected> 3}
p13.then(null, (err) => setTimeout(console.log, 0, err)) // 3,参数传给后续回绝处理程序
Promise.reject()
不是幂等的(与Promise.resolve()
不同),如果参数为期约对象,则该期约会成为返回的回绝理由
setTimeout(console.log, 0, Promise.reject(Promise.resolve())) // Promise {<rejected>: Promise}
同步 / 异步执行的二元性
-
因为期约的 异步个性,其尽管是同步对象(可在同步执行模式中应用),但也是异步执行模式的媒介
- 同步线程的代码无奈捕捉回绝的期约 ,回绝期约的谬误会通过 浏览器异步音讯队列 来解决
- 代码一旦开始以异步模式执行,惟一与之交互的形式就是 应用异步构造,即期约
try {throw new Error('foo') // 同步线程抛出谬误
} catch (error) {console.log(error + '1') // Error: foo1,同步线程捕捉谬误
}
try {Promise.reject('bar') // 同步线程使用期约
} catch (error) {console.log(error + '2') // 同步线程捕捉不到回绝的期约
}
// Promise {<rejected>: "bar"},浏览器异步音讯队列捕捉到回绝的期约
// Uncaught (in promise) bar
期约的实例办法
- 这些办法能够拜访异步操作返回的数据,解决期约胜利和失败的后果
实现 Thenable 接口
- ECMAScript 裸露的异步构造中,任何对象都有一个
then()
办法,该办法被认为实现了thenable
接口
class MyThenable {
// 实现 Thenable 接口的最简略的类
then() {}
}
Promise.prototype.then()
-
Promise.prototype.then()
为期约 增加处理程序 ,接管2 个可选的处理程序参数onResolved
和onRejected
onResolved
会在期约进入 兑现 状态时执行onRejected
会在期约进入 回绝 状态时执行
function onResolved(id) {setTimeout(console.log, 0, id, 'resolved')
}
function onRejected(id) {setTimeout(console.log, 0, id, 'rejected')
}
let p14 = new Promise((resolve, reject) => {setTimeout(resolve, 3000)
})
let p15 = new Promise((resolve, reject) => {setTimeout(reject, 3000)
})
p14.then(() => {onResolved('p14') // 'p14 resolved'(3 秒后)},
() => {onRejected('p14')
}
)
p15.then(() => {onResolved('p15')
},
() => {onRejected('p15') // 'p15 rejected'(3 秒后)}
)
- 传给
then()
的任何非函数类型的参数都会被静默疏忽(不举荐),如果只提供onResolved
或onRejected
,个别在另一个参数地位上传入null
或undefined
p14.then('gobbeltygook') // 参数不是对象,静默疏忽
p14.then(() => onResolved('p14')) // 'p14 resolved'(3 秒后),不传 onRejected
p15.then(null, () => onRejected('p15')) // 'p15 rejected'(3 秒后),不传 onResolved
-
Promise.prototype.then()
返回一个 新的期约实例 ,该实例基于onResolved
处理程序(Promise.resolved()
包装)的返回值构建- 若 没有提供这个处理程序 ,则包装 上一个 期约解决之后的值(父期约的传递)
- 若提供了处理程序,但 没有显示的返回语句,则包装默认的返回值
undefined
- 若提供了处理程序,且 有显示的返回值 ,则包装 这个值
- 若提供了处理程序,且 返回期约 ,则包装 返回的期约
- 若提供了处理程序,且 抛出异样 ,则包装 回绝的期约
- 若提供了处理程序,且 返回谬误值 ,则把 谬误对象 包装在一个 解决的期约 中(而非回绝的期约)
let p16 = Promise.resolve('foo')
let result1 = p16.then() // 没有提供处理程序
setTimeout(console.log, 0, result1) // Promise {<fulfilled>: 'foo'},包装上一个期约解决后的值
let result2 = p16.then(() => undefined) // 处理程序没有显示的返回语句
let result3 = p16.then(() => {}) // 处理程序没有显示的返回语句
let result4 = p16.then(() => Promise.resolve()) // 处理程序没有显示的返回语句
setTimeout(console.log, 0, result2) // Promise {<fulfilled>: undefined},包装默认返回值 undefined
setTimeout(console.log, 0, result3) // Promise {<fulfilled>: undefined},包装默认返回值 undefined
setTimeout(console.log, 0, result4) // Promise {<fulfilled>: undefined},包装默认返回值 undefined
let result5 = p16.then(() => 'bar') // 处理程序有显示的返回值
let result6 = p16.then(() => Promise.resolve('bar')) // 处理程序有显示的返回值
setTimeout(console.log, 0, result5) // Promise {<fulfilled>: 'bar'},包装这个值
setTimeout(console.log, 0, result6) // Promise {<fulfilled>: 'bar'},包装这个值
let result7 = p16.then(() => new Promise(() => {})) // 处理程序返回一个待定的期约
let result8 = p16.then(() => Promise.reject('bar')) // 处理程序返回一个回绝的期约
// Uncaught (in promise) bar
setTimeout(console.log, 0, result7) // Promise {<pending>},包装返回的期约
setTimeout(console.log, 0, result8) // Promise {<rejected>: 'bar'},包装返回的期约
let result9 = p16.then(() => {throw 'baz' // 处理程序抛出异样})
// Uncaught (in promise) baz
setTimeout(console.log, 0, result9) // Promise {<rejected>: 'baz'},包装回绝的期约
let result10 = p16.then(() => Error('qux')) // 处理程序返回谬误值
setTimeout(console.log, 0, result10) // Promise {<fulfilled>: Error: qux},把谬误对象包装在一个解决的期约中
- 与
onResolved
雷同,onRejected
处理程序作为参数时,其返回的值也被Promise.resolve()
包装,返回 新的期约实例
let p17 = Promise.reject('foo')
let result11 = p17.then() // 没有提供处理程序
// Uncaught (in promise) foo
setTimeout(console.log, 0, result11) // Promise {<rejected>: 'foo'},包装上一个期约解决后的值
let result12 = p17.then(null, () => undefined) // 处理程序没有显示的返回语句
let result13 = p17.then(null, () => {}) // 处理程序没有显示的返回语句
let result14 = p17.then(null, () => Promise.resolve()) // 处理程序没有显示的返回语句
setTimeout(console.log, 0, result12) // Promise {<fulfilled>: undefined},包装默认返回值 undefined
setTimeout(console.log, 0, result13) // Promise {<fulfilled>: undefined},包装默认返回值 undefined
setTimeout(console.log, 0, result14) // Promise {<fulfilled>: undefined},包装默认返回值 undefined
let result15 = p17.then(null, () => 'bar') // 处理程序有显示的返回值
let result16 = p17.then(null, () => Promise.resolve('bar')) // 处理程序有显示的返回值
setTimeout(console.log, 0, result15) // Promise {<fulfilled>: 'bar'},包装这个值
setTimeout(console.log, 0, result16) // Promise {<fulfilled>: 'bar'},包装这个值
let result17 = p17.then(null, () => new Promise(() => {})) // 处理程序返回一个待定的期约
let result18 = p17.then(null, () => Promise.reject('bar')) // 处理程序返回一个回绝的期约
// Uncaught (in promise) bar
setTimeout(console.log, 0, result17) // Promise {<pending>},包装返回的期约
setTimeout(console.log, 0, result18) // Promise {<rejected>: 'bar'},包装返回的期约
let result19 = p17.then(null, () => {throw 'baz' // 处理程序抛出异样})
// Uncaught (in promise) baz
setTimeout(console.log, 0, result19) // Promise {<rejected>: 'baz'},包装回绝的期约
let result20 = p17.then(null, () => Error('qux')) // 处理程序返回谬误值
setTimeout(console.log, 0, result20) // Promise {<fulfilled>: Error: qux},把谬误对象包装在一个解决的期约中
Promise.prototype.catch()
-
Promise.prototype.catch()
为期约 增加回绝处理程序,接管1 个可选的处理程序参数onRejected
- 该办法相当于调用
Promise.prototypr.then(null, onRejected)
- 该办法办法也返回 新的期约实例 ,其行为与
Promise.prototype.then()
的onRejeted
处理程序一样
- 该办法相当于调用
let p18 = Promise.reject()
let onRejected2 = function () {setTimeout(console.log, 0, 'reject')
}
p18.then(null, onRejected2) // 'reject'
p18.catch(onRejected2) // 'reject',两种增加回绝处理程序的形式是一样的
Promise.prototype.finally()
-
Promise.prototype.finally()
为期约 增加 onFinally 处理程序,接管1 个可选的处理程序参数onFinally
- 无论期约转换为 解决 还是 回绝 状态,
onFinally
处理程序 都会执行,但其无奈晓得期约的状态 - 该办法次要用于 增加清理代码
- 无论期约转换为 解决 还是 回绝 状态,
let p19 = Promise.resolve()
let p20 = Promise.reject()
let onFinally = function () {setTimeout(console.log, 0, 'Finally')
}
p19.finally(onFinally) // 'Finally'
p20.finally(onFinally) // 'Finally'
-
Promise.prototype.finally()
返回一个 新的期约实例 ,其以下状况均包装 父期约的传递- 未提供处理程序
- 提供了处理程序,但没有显示的返回语句
- 提供了处理程序,且有显示的返回值
- 处理程序返回一个解决的期约
- 处理程序返回谬误值
let p21 = Promise.resolve('foo')
let result23 = p21.finally() // 未提供处理程序
let result24 = p21.finally(() => undefined) // 提供了处理程序,但没有显示的返回语句
let result25 = p21.finally(() => {}) // 提供了处理程序,但没有显示的返回语句
let result26 = p21.finally(() => Promise.resolve()) // 提供了处理程序,但没有显示的返回语句
let result27 = p21.finally(() => 'bar') // 提供了处理程序,且有显示的返回值
let result28 = p21.finally(() => Promise.resolve('bar')) // 处理程序返回一个解决的期约
let result29 = p21.finally(() => Error('qux')) // 处理程序返回谬误值
setTimeout(console.log, 0, result23) // Promise {<fulfilled>: 'foo'},包装父期约的传递
setTimeout(console.log, 0, result24) // Promise {<fulfilled>: 'foo'},包装父期约的传递
setTimeout(console.log, 0, result25) // Promise {<fulfilled>: 'foo'},包装父期约的传递
setTimeout(console.log, 0, result26) // Promise {<fulfilled>: 'foo'},包装父期约的传递
setTimeout(console.log, 0, result27) // Promise {<fulfilled>: 'foo'},包装父期约的传递
setTimeout(console.log, 0, result28) // Promise {<fulfilled>: 'foo'},包装父期约的传递
setTimeout(console.log, 0, result29) // Promise {<fulfilled>: 'foo'},包装父期约的传递
- 若
onFinally
处理程序返回 待定或回绝的期约 或抛出谬误 ,则返回值包装 相应的期约(抛出谬误包装回绝的期约)
let result30 = p21.finally(() => new Promise(() => {})) // 处理程序返回一个待定的期约
let result31 = p21.finally(() => Promise.reject()) // 处理程序返回一个回绝的期约
// Uncaught (in promise) undefined
let result32 = p21.finally(() => {throw 'baz' // 处理程序抛出谬误})
// Uncaught (in promise) baz
setTimeout(console.log, 0, result30) // Promise {<pending>},返回相应的期约
setTimeout(console.log, 0, result31) // Promise {<rejected>: undefined},返回相应的期约
setTimeout(console.log, 0, result32) // Promise {<rejected>: 'baz'},返回相应的期约
onFinally
处理程序返回 待定的期约解决后,新期约实例仍后传初始的期约
let p22 = Promise.resolve('foo')
let p23 = p22.finally(() => new Promise((resolve, reject) => setTimeout(() => resolve('bar'), 100)) // 处理程序返回一个待定的期约(100 毫秒后解决))
setTimeout(console.log, 0, p23) // Promise {<pending>},返回相应的期约
setTimeout(() => setTimeout(console.log, 0, p23), 200) // Promise {<fulfilled>: "foo"},(200 毫秒后)待定的期约已解决
非重入期约办法
- 期约进入 落定(解决 / 回绝)状态时,与该状态相干的 处理程序不会立刻执行 ,处理程序后的 同步代码 会在其之前 先执行 ,该个性称为 非重入
let p24 = Promise.resolve() // 解决的期约,已落定
p24.then(() => console.log('onResolved handler')) // 与期约状态相干的 onResolved 处理程序
console.log('then() returns') // 处理程序之后的同步代码
/*
'then() returns',处理程序之后的同步代码先执行
'onResolved handler'
*/
- 即便期约在 处理程序之后 才扭转状态(解决 / 回绝),处理程序仍体现 非重入 个性
let synchronousResolve // 全局办法:期约状态状态
let p25 = new Promise((resolve) => {synchronousResolve = function () {console.log('1: invoking resolve()')
resolve() // 期约状态扭转
console.log('2: resolve() returns')
}
})
p25.then(() => console.log('4: then() handler executes')) // 与期约状态相干的 onResolved 处理程序
synchronousResolve() // 处理程序之后的同步代码:期约状态扭转
console.log('3: synchronousResolve() returns') // 处理程序之后的同步代码
/*
'1: invoking resolve()'
'2: resolve() returns'
'3: synchronousResolve() returns'
'4: then() handler executes'
*/
- 非重入个性实用于
onResolved
、onRejected
、catch()
、finally()
处理程序
let p26 = Promise.resolve()
p26.then(() => console.log('p26.then() onResolved'))
console.log('p26.then() returns')
let p27 = Promise.reject()
p27.then(null, () => console.log('p27.then() onRejected'))
console.log('p27.then() returns')
let p28 = Promise.reject()
p28.catch(() => console.log('p28.catch() onRejected'))
console.log('p28.catch() returns')
let p29 = Promise.resolve()
p26.finally(() => console.log('p29.finally() onFinally'))
console.log('p29.finally() returns')
/*
'p26.then() returns'
'p27.then() returns'
'p28.catch() returns'
'p29.finally() returns'
'p26.then() onResolved'
'p27.then() onRejected'
'p28.catch() onRejected'
'p29.finally() onFinally'
*/
邻近处理程序的执行程序
-
若期约增加了 多个处理程序 ,当期约状态变动时,处理程序按 增加程序 顺次执行
then()
、catch()
、finally()
增加的处理程序均如此
let p30 = Promise.resolve()
let p31 = Promise.reject()
p30.then(() => setTimeout(console.log, 0, 1))
p30.then(() => setTimeout(console.log, 0, 2))
p31.then(null, () => setTimeout(console.log, 0, 3))
p31.then(null, () => setTimeout(console.log, 0, 4))
p31.catch(() => setTimeout(console.log, 0, 5))
p31.catch(() => setTimeout(console.log, 0, 6))
p30.finally(() => setTimeout(console.log, 0, 7))
p30.finally(() => setTimeout(console.log, 0, 8))
/*
1
2
3
4
5
6
7
8
*/
传递解决值和回绝理由
-
期约进入 落定(解决 / 回绝)状态后,会给处理程序提供 解决值(兑现)或 回绝理由(回绝)
- 在 执行函数 中,解决值和回绝理由别离作为
resolve()
和reject()
的 首个参数 ,传给onResolved
和onRejected
处理程序(作为其 惟一参数) - 在
Promise.resolve()
和Promise.reject()
被调用时,接管到的解决值和回绝理由同样向后传递给处理程序(作为其 惟一参数)
- 在 执行函数 中,解决值和回绝理由别离作为
let p32 = new Promise((resolve, reject) => resolve('foo')) // 执行函数中
p32.then((value) => console.log(value)) // 'foo'
let p33 = new Promise((resolve, reject) => reject('bar')) // 执行函数中
p33.catch((reason) => console.log(reason)) // 'bar'
let p34 = Promise.resolve('foo') // Promise.resolve()中
p34.then((value) => console.log(value)) // 'foo'
let p35 = Promise.reject('bar') // Promise.reject()中
p35.catch((reason) => console.log(reason)) // 'bar'
回绝期约与回绝错误处理
- 在期约的 执行函数 或处理程序 中抛出谬误会导致回绝,谬误对象 成为 回绝理由
let p36 = new Promise((resolve, reject) => reject(Error('foo'))) // 在执行函数中抛出谬误
let p37 = new Promise((resolve, reject) => {throw Error('foo') // 在执行函数中抛出谬误
})
let p38 = Promise.resolve().then(() => {throw Error('foo') // 在处理程序中抛出谬误
})
let p39 = Promise.reject(Error('foo')) // 在回绝的期约中抛出谬误
setTimeout(console.log, 0, p36) // Promise {<rejected>: Error: foo
setTimeout(console.log, 0, p37) // Promise {<rejected>: Error: foo
setTimeout(console.log, 0, p38) // Promise {<rejected>: Error: foo
setTimeout(console.log, 0, p39) // Promise {<rejected>: Error: foo
- 能够以 任何理由 回绝,包含
undefined
,但最好 对立应用谬误对象 ,谬误对象能够让浏览器捕捉其中的 栈追踪信息 -
如上述回绝期约,会在浏览器抛出 4 个未捕捉谬误:
Promise.resolve().then()
的谬误最初才呈现,因为须要在运行时 增加处理程序(即未捕捉前创立另一个新期约)
/* 上述回绝期约会抛出 4 个未捕捉谬误:栈追踪信息
Uncaught (in promise) Error: foo
at <anonymous>:1:51
at new Promise (<anonymous>)
at <anonymous>:1:11
(anonymous) @ VM1402:1
(anonymous) @ VM1402:1
Uncaught (in promise) Error: foo
at <anonymous>:3:9
at new Promise (<anonymous>)
at <anonymous>:2:11
(anonymous) @ VM1402:3
(anonymous) @ VM1402:2
Uncaught (in promise) Error: foo
at <anonymous>:8:26
(anonymous) @ VM1402:8
Uncaught (in promise) Error: foo
at <anonymous>:6:9
(anonymous) @ VM1402:6
Promise.then (async)
(anonymous) @ VM1402:5
*/
-
异步谬误 的机制与同步是不同的:
- 同步代码通过
throw()
关键字抛出谬误时,会进行执行后续任何命令 - 在期约中抛出谬误时,不会阻止同步指令,其 谬误 也只能通过异步的
onRejected
处理程序 捕捉
- 同步代码通过
throw Error('foo') // 同步代码抛出谬误(try/catch 中能捕捉)console.log('bar') // 后续任何指令不再执行
// Uncaught Error: foo,浏览器音讯队列
Promise.reject(Error('foo')) // 期约中抛出谬误(try/catch 中捕捉不到)console.log('bar') // 'bar',同步指令继续执行
// Uncaught (in promise) Error: foo,浏览器音讯队列
Promise.reject(Error('foo')).catch((e) => {console.log(e) // 'Error: foo',在期约中捕捉
})
- 执行函数 中的谬误,在 解决或回绝期约之前 ,仍可用
try/catch
捕捉
let p40 = new Promise((resolve, reject) => {
try {throw Error('foo')
} catch (error) {}
resolve('bar')
})
setTimeout(console.log, 0, p40) // Promise {<fulfilled>: 'bar'}
then()
和catch()
的onRejected
处理程序在语义上与try/catch
雷同(捕捉谬误后将其隔离,不影响失常逻辑),因而onReject
处理程序在捕捉异步谬误后 返回一个解决的期约
console.log('begin synchronous execution')
try {throw Error('foo') // 抛出同步谬误
} catch (error) {console.log('caught error', error) // 捕捉同步谬误
}
console.log('continue synchronous execution')
/*
'begin synchronous execution'
'caught error Error: foo'
'continue synchronous execution'
*/
new Promise((resolve, reject) => {console.log('begin synchronous execution')
reject(Error('bar')) // 抛出异步谬误
})
.catch((e) => {console.log('caught error', e) // 捕捉异步谬误
})
.then(() => {console.log('continue synchronous execution')
})
/*
期约连锁与期约合成
- 多个期约在一起能够形成弱小的代码逻辑:期约连锁(拼接)与 期约合成(组合)
期约连锁
- 每个期约的实例办法(
then()
、catch()
、finally()
)都返回新的期约实例,多个期约可连缀调用造成 期约连锁
let p41 = new Promise((resolve, reject) => {console.log('first')
resolve()})
p41
.then(() => console.log('second'))
.then(() => console.log('third'))
.then(() => console.log('fourth'))
/*
'first'
'second'
'third'
'fourth'
*/
- 若想 串行化异步工作 ,需让 每个执行器都返回期约实例,
let p42 = new Promise((resolve, reject) => {console.log('p42 first')
setTimeout(resolve, 1000)
})
p42
.then(() =>
// 执行器返回期约实例
new Promise((resolve, reject) => {console.log('p42 second')
setTimeout(resolve, 1000)
})
)
.then(() =>
// 执行器返回期约实例
new Promise((resolve, reject) => {console.log('p42 third')
setTimeout(resolve, 1000)
})
)
.then(() =>
// 执行器返回期约实例
new Promise((resolve, reject) => {console.log('p42 fourth')
setTimeout(resolve, 1000)
})
)
/*
'p42 first'(1 秒后)'p42 second'(2 秒后)'p42 third'(3 秒后)'p42 fourth'(4 秒后)*/
- 可把生成期约的同样的代码 封装 到一个工厂函数中
function delayedResolve(str) {return new Promise((resolve, reject) => {console.log(str)
setTimeout(resolve, 1000)
})
}
delayedResolve('p42 first')
.then(() => delayedResolve('p42 second'))
.then(() => delayedResolve('p42 third'))
.then(() => delayedResolve('p42 fourth'))
/*
'p42 first'(1 秒后)'p42 second'(2 秒后)'p42 third'(3 秒后)'p42 fourth'(4 秒后)*/
- 期约连锁能无效 解决回调天堂 问题,上述代码如不必期约的状况如下
function delayedNotPromise(str, callback = null) {setTimeout(() => {console.log(str)
callback && callback()}, 1000)
}
delayedNotPromise('p42 first', () => {delayedNotPromise('p42 second', () => {delayedNotPromise('p42 third', () => {delayedNotPromise('p42 fourth', () => {})
})
})
})
/*
'p42 first'(1 秒后)'p42 second'(2 秒后)'p42 third'(3 秒后)'p42 fourth'(4 秒后)*/
then()
、catch()
、finally()
都返回新的期约实例,可任意进行期约连锁
let p43 = new Promise((resolve, reject) => {console.log('p43')
reject()})
p43
.catch(() => console.log('p43 catch'))
.then(() => console.log('p43 then'))
.finally(() => console.log('p43 finally'))
/*
'p43'
'p43 catch'
'p43 then'
'p43 finally'
*/
期约图
- 一个期约能够有任意多个处理程序,期约连锁能够构建 有向非循环图
- A
- B
- D
- E
- C
- F
- G
let A = new Promise((resolve, reject) => {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('F'))
/*
'A'
'B'
'C'
'D'
'E'
'F'
*/
Promise.all()和 Promise.race()
-
Promise.all()
接管一个 可迭代对象 (必传),返回一个 新期约 ,其创立的期约会在 一组期约全副解决之后 再解决- 可迭代对象中的元素通过
Promise.resolve()
转换为期约 - 空迭代对象等价于
Promise.resolve()
- 可迭代对象中的元素通过
Promise.all([Promise.resolve(), Promise.resolve()]) // 接管 1 组可迭代对象
Promise.all([3, 4]) // 可迭代对象中的元素通过 Promise.resolve()转换为期约
Promise.all([]) // 空迭代对象等价于 Promise.resolve()
Promise.all() // TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator)),参数必填
Promise.all()
合成的期约只会在 每个蕴含期约都解决后 才解决
let p44 = Promise.all([Promise.resolve(),
new Promise((resolve, reject) => setTimeout(resolve, 1000)),
])
p44.then(() => setTimeout(console.log, 0, 'all() resolved!')) // 'all() resolved!'(1 秒后,非 0 秒,需等蕴含的期约先解决)
- 若有 1 个蕴含的期约待定,则合成待定的期约,回绝也同理(回绝优先于待定)
let p45 = Promise.all([new Promise(() => {}), Promise.resolve()]) // 蕴含的期约有待定的
setTimeout(console.log, 0, p45) // Promise {<pending>},合成待定的期约
let p46 = Promise.all([new Promise(() => {}), Promise.reject()]) // 蕴含的期约有回绝的(也有待定的)// Uncaught (in promise) undefined
setTimeout(console.log, 0, p46) // Promise {<rejected>: undefined},合成回绝的期约
- 若蕴含的所有期约都胜利解决,则合成解决的期约,解决值是所有蕴含期约的解决值的数组(按迭代器程序)
let p47 = Promise.all([Promise.resolve(1),
Promise.resolve(),
Promise.resolve(3),
]) // 蕴含的所有期约都解决
setTimeout(console.log, 0, p47) // Promise {<fulfilled>: [1, undefined, 3]}
- 若有期约回绝,第一个 回绝的期约会将本人的理由作为 合成期约的回绝理由 (后续理由不再影响合成期约的回绝理由),但 不影响 后续期约的回绝操作
let p48 = Promise.all([Promise.reject(3), // 第一个回绝的期约,回绝理由为 3
new Promise((resolve, reject) => setTimeout(reject, 1000, 4)), // 第二个回绝的期约,回绝理由为 4
])
// Uncaught (in promise) 3
setTimeout(console.log, 0, p48) // Promise {<rejected>: 3},第一个回绝理由作为合成期约的回绝理由
p48.catch((reason) => setTimeout(console.log, 2000, reason)) // 3,第一个回绝理由作为合成期约的回绝理由,但浏览器不会显示未解决的谬误(Uncaught (in promise) 3)
-
Promise.race()
与Promise.all()
相似,接管一个 可迭代对象 (必传),包装汇合中 最先落定(解决或回绝)期约解决值或回绝理由并返回 新期约- 可迭代对象中的元素通过
Promise.resolve()
转换为期约 - 空迭代对象等价于
Promise.resolve()
- 迭代程序决定落定程序
- 第一个回绝的期约会将本人的理由作为 合成期约的回绝理由 ,但 不影响 ** 后续期约的回绝操作
- 可迭代对象中的元素通过
Promise.race([Promise.resolve(), Promise.resolve()]) // 接管 1 组可迭代对象
Promise.race([3, 4]) // 可迭代对象中的元素通过 Promise.resolve()转换为期约
Promise.race([]) // 空迭代对象等价于 Promise.resolve()
// Promise.all() // TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator)),参数必填
let p49 = Promise.race([Promise.resolve(3),
new Promise((resolve, reject) => setTimeout(reject, 1000)),
])
setTimeout(console.log, 0, p49) // Promise {<fulfilled>: 3},解决先产生,超时后的回绝被疏忽
let p50 = Promise.race([Promise.reject(4),
new Promise((resolve, reject) => setTimeout(resolve, 1000)),
])
// Uncaught (in promise) 4
setTimeout(console.log, 0, p50) // Promise {<rejected>: 4},回绝先产生,超时后的解决被疏忽
let p51 = Promise.race([Promise.resolve(1),
Promise.resolve(),
Promise.resolve(3),
])
setTimeout(console.log, 0, p51) // Promise {<fulfilled>: 1},迭代程序决定落定程序
let p52 = Promise.race([Promise.reject(3), // 第一个回绝的期约,回绝理由为 3
new Promise((resolve, reject) => setTimeout(reject, 1000, 4)), // 第二个回绝的期约,回绝理由为 4
])
// Uncaught (in promise) 3
setTimeout(console.log, 0, p52) // Promise {<rejected>: 3},第一个回绝理由作为合成期约的回绝理由
p52.catch((reason) => setTimeout(console.log, 2000, reason)) // 3,第一个回绝理由作为合成期约的回绝理由,但浏览器不会显示未解决的谬误(Uncaught (in promise) 3)
串行期约合成
- 多个函数能够合成为一个函数
function addTwo(x) {return x + 2}
function addThree(x) {return x + 3}
function addFive(x) {return x + 5}
function addTen(x) {return addFive(addThree(addTwo(x)))
}
console.log(addTen(7)) // 17
- 与函数合成相似,期约能够 异步产生值 并将其 传给处理程序 ,后续期约可用 之后期约的返回值 来串联期约
function addTen(x) {return Promise.resolve(x).then(addTwo).then(addThree).then(addFive)
}
setTimeout(console.log, 0, addTen(8)) // Promise {<fulfilled>: 18}
addTen(8).then((result) => console.log(result)) // 18
- 可应用
Array.prototype.reduce()
简写上述期约串联
function addTen(x) {return [addTwo, addThree, addFive].reduce((pre, cur) => {return pre.then(cur)
}, Promise.resolve(x)) // 归并终点值(归并函数第 1 个参数)为 Promise.resolve(x),第 2 个参数为数组第 1 项 addTwo
}
setTimeout(console.log, 0, addTen(9)) // Promise {<fulfilled>: 19}
addTen(9).then((result) => console.log(result)) // 19
- 可将其最终 封装 成一个 通用办法
function compose(...fns) {return (x) =>
fns.reduce((pre, cur) => {return pre.then(cur)
}, Promise.resolve(x))
}
addTen = compose(addTwo, addThree, addFive)
addTen(10).then((result) => console.log(result)) // 20
期约扩大
- 期约有其不足之处,ECMAScript 未波及的两个个性 期约勾销 和进度追踪 在很多第三方期约库中已实现
期约勾销
- 能够提供一种临时性的 封装 ,以实现 勾销期约 的性能
const startButton = document.querySelector('#start') // 开始按钮
const cancelButton = document.querySelector('#cancel') // 完结按钮
let cancelBtnHasClickEvent = false // 完结按钮是否已增加点击事件
/*
书中案例每次点击“开始”按钮,都会从新实例化 CancelToken 实例,给 cancelToken 追加一个点击事件,打印的 'delay cancelled' 会随之越来越多
这里追加一个全局变量 cancelBtnHasClickEvent,确保只在首次点击“开始”按钮时,给 cancelToken 只追加一次点击事件
*/
// CancelToken 类,包装一个期约,把解决办法裸露给 cancelFn 参数
class CancelToken {constructor(cancelFn) {this.promise = new Promise((resolve, reject) => {cancelFn(() => {setTimeout(console.log, 0, 'delay cancelled') // 勾销计时
resolve() // 期约解决})
})
}
}
// 点击事件:开始计时、实例化新的 CancelToken 实例
function cancellabelDelayedResolve(delay) {setTimeout(console.log, 0, 'set delay') // 开始计时
return new Promise((resolve, reject) => {const id = setTimeout(() => {setTimeout(console.log, 0, 'delay resolve') // 经延时后触发
resolve()}, delay)
// 实例化新的 CancelToken 实例
const cancelToken = new CancelToken((cancelCallback) => {
cancelBtnHasClickEvent === false &&
cancelButton.addEventListener('click', cancelCallback) // 完结按钮增加点击事件
cancelBtnHasClickEvent = true // 完结按钮已增加点击事件
})
cancelToken.promise.then(() => clearTimeout(id)) // 触发令牌实例中的期约解决
})
}
startButton.addEventListener('click', () => cancellabelDelayedResolve(1000)) // 开始按钮增加点击事件
残缺文件 →
期约进度告诉
- ES6 不反对监控期约的执行进度,可通过扩大来实现
// 子类 TrackablePromise,继承父类 Promise
class TrackablePromise extends Promise {
// 子类构造函数,接管 1 个参数(executor 函数)constructor(executor) {const notifyHandlers = []
// super()调用父类构造函数 constructor(),传入参数(执行器函数)super((resolve, reject) => {// 执行 executor()函数,参数为传给 TrackablePromise 子类的参数,返回执行的后果
return executor(resolve, reject, (status) => {console.log(status)
/*
'80% remaining'(约 1 秒后)'60% remaining'(约 2 秒后)'remaining'(约 3 秒后)'remaining'(约 4 秒后)*/
notifyHandlers.map((handler) => {return handler(status)
})
})
})
this.notifyHandlers = notifyHandlers
}
// 增加 notify 办法,接管 1 个参数(notifyhandler 函数)notify(notifyHandler) {this.notifyHandlers.push(notifyHandler)
return this
}
}
// 创立子类实例,传入参数(executor 函数)let p53 = new TrackablePromise((resolve, reject, notify) => {function countdown(x) {if (x > 0) {notify(`${20 * x}% remaining`)
setTimeout(() => countdown(x - 1), 1000)
} else {resolve()
}
}
countdown(5)
})
console.log(p53) // Promise {<pending>, notifyHandlers: Array(0)},TrackablePromise 实例(子类期约)p53.notify((x) => setTimeout(console.log, 0, 'progress:', x)) // 调用期约实例的 notify()办法,传入参数(notifyhandler 函数)p53.then(() => setTimeout(console.log, 0, 'completed')) // 调用期约实例的 then()办法,传入参数(onResolved 处理程序)/*
'progress: 80% remaining'(约 1 秒后)'progress: 60% remaining'(约 2 秒后)'progress: 40% remaining'(约 3 秒后)'progress: 20% remaining'(约 4 秒后)'completed'(约 5 秒后)*/
p53
.notify((x) => setTimeout(console.log, 0, 'a:', x))
.notify((x) => setTimeout(console.log, 0, 'b:', x)) // notice()返回期约,连缀调用
p53.then(() => setTimeout(console.log, 0, 'completed'))
/*
'a: 80% remaining'(约 1 秒后)'b: 80% remaining'(约 1 秒后)'a: 60% remaining'(约 2 秒后)'b: 60% remaining'(约 2 秒后)'a: 40% remaining'(约 3 秒后)'b: 40% remaining'(约 3 秒后)'a: 20% remaining'(约 4 秒后)'b: 20% remaining'(约 4 秒后)'completed'(约 5 秒后)*/
总结 & 问点
- 什么是 Promise 类型?如何创立?其不同状态别离示意什么?
- 执行器函数负责的作用是什么?如何推延其切换状态?如何防止期约卡在待定状态?
- 如何实例化一个解决的期约?其值是什么?若传入的参数也是期约后果会怎么?
- 如何实例化一个回绝的期约?其回绝理由是什么?若传入的参数也是期约后果会怎么?
- Promise.prototype.then()、Promise.prototype.catch()、Promise.prototype.finally()的含意别离是什么?别离接管哪些参数?依据参数的不同,其返回值别离有哪些状况?
- 如何了解期约的“非重入”?其实用于哪些处理程序?
- 若同一个期约增加了多个处理程序,当其状态变动时处理程序按怎么的程序执行?如何把期约的解决值和回绝理由传递给处理程序?
- 如何传递解决值和回绝理由?如何在抛出谬误时捕捉谬误对象?为什么回绝理由最好对立应用谬误对象?
- 写一段代码,别离在【try/catch 和期约】中捕捉【同步和异步】的谬误,且不影响失常的其余(后续)代码逻辑
- 写一段代码,实现多个异步工作的串行:① 不必期约,造成“回调天堂”② 用期约连锁解决这个问题
- Promise.all()和 Promise.race()的含意是什么?其在不同状况下别离返回怎么的期约?
- 写一段代码,串联多个期约,再用 reduce()简化其代码,最初将其封装成一个通用办法
- 写一段代码实现以下性能:页面有 2 个按钮【开始】和【完结】,单击【开始】实例化一个新期约并打印“开始”,1 秒前期约兑现并打印“胜利”,单击【完结】勾销该期约并打印“完结”