关于javascript:javascript高级程序设计学习笔记-112期约

32次阅读

共计 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

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

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

    • 通过 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 个可选的处理程序参数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 秒后),不传 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'
*/
  • 非重入个性实用于 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: 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 秒前期约兑现并打印“胜利”,单击【完结】勾销该期约并打印“完结”

正文完
 0