关注前端小讴,浏览更多原创技术文章

期约

  • 期约是对尚不存在后果的一个替身,是一种异步程序执行的机制

相干代码 →

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

通过执行函数管制期约状态

  • 期约的状态是公有的,只能在执行器函数中实现外部操作
  • 执行器函数负责初始化期约异步行为管制状态转换

    • 通过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)) // truesetTimeout(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 个可选的处理程序参数onResolvedonRejected

    • 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()的任何非函数类型的参数都会被静默疏忽(不举荐),如果只提供onResolvedonRejected,个别在另一个参数地位上传入nullundefined
p14.then('gobbeltygook') // 参数不是对象,静默疏忽p14.then(() => onResolved('p14')) // 'p14 resolved'(3秒后),不传onRejectedp15.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},包装默认返回值undefinedsetTimeout(console.log, 0, result3) // Promise {<fulfilled>: undefined},包装默认返回值undefinedsetTimeout(console.log, 0, result4) // Promise {<fulfilled>: undefined},包装默认返回值undefinedlet 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) barsetTimeout(console.log, 0, result7) // Promise {<pending>},包装返回的期约setTimeout(console.log, 0, result8) // Promise {<rejected>: 'bar'},包装返回的期约let result9 = p16.then(() => {  throw 'baz' // 处理程序抛出异样})// Uncaught (in promise) bazsetTimeout(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) foosetTimeout(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},包装默认返回值undefinedsetTimeout(console.log, 0, result13) // Promise {<fulfilled>: undefined},包装默认返回值undefinedsetTimeout(console.log, 0, result14) // Promise {<fulfilled>: undefined},包装默认返回值undefinedlet 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) barsetTimeout(console.log, 0, result17) // Promise {<pending>},包装返回的期约setTimeout(console.log, 0, result18) // Promise {<rejected>: 'bar'},包装返回的期约let result19 = p17.then(null, () => {  throw 'baz' // 处理程序抛出异样})// Uncaught (in promise) bazsetTimeout(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) undefinedlet result32 = p21.finally(() => {  throw 'baz' // 处理程序抛出谬误})// Uncaught (in promise) bazsetTimeout(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'*/
  • 非重入个性实用于onResolvedonRejectedcatch()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()首个参数,传给onResolvedonRejected处理程序(作为其惟一参数
    • 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: foosetTimeout(console.log, 0, p37) // Promise {<rejected>: Error: foosetTimeout(console.log, 0, p38) // Promise {<rejected>: Error: foosetTimeout(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) undefinedsetTimeout(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) 3setTimeout(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) 4setTimeout(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) 3setTimeout(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,继承父类Promiseclass 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 秒前期约兑现并打印“胜利”,单击【完结】勾销该期约并打印“完结”