《JavaScript 高级程序设计》第8-9章 函数&期约与异步函数

8.函数

function foo() {}
console.log(foo.bind(null).name) // bound foo

let dog = {
  years: 1,
  get age() {
    return this.years
  }
  set age(newAge) {
    this.years = newAge
  }
}
let propertyDescriptor = Object.getPropertyDescriptor(dog, 'age')
console.log(propertyDescriptor.get.name) // get age
console.log(propertyDescriptor.set.name) // set age

9.期约与异步函数

异步编程

期约(Promise)

期约基础

ES6 新增的引用类型 Promise,可以通过 new 操作符来实例化并传入执行器(executor)函数作为参数。 期约是一个有状态的对象,处于以下3种状态之一:

  1. 待定(pending)
  2. 兑现(fulfilled,也称“解决”,resolved)
  3. 拒绝(rejected)

待定是期约的最初始状态。在待定状态下,期约可以落定(settled)为代表成功的兑现状态或代表失败的拒绝状态。期约的状态是私有的,不能直接通过 JavaScript 检测到。 期约主要有两大用途。首先是抽象地表示一个异步操作,通过期约的状态表示期约是否完成。其次,期约封装的异步操作会实际生成某个值,在程序期待期约状态改变时可以访问这个值,如果被拒绝则期待拿到拒绝的理由。 为了支持上述用例,每个期约只要状态切换为兑现,就会有一个私有的内部值(value)。如果状态切换为拒绝则会有一个私有的内部理由(reason)。无论是值还是理由都是包含原始值或对象的不可修改的引用。 由于期约的状态是私有的,所以只能在期约的执行器函数中进行操作。执行器函数主要有两项职责:初始化期约的异步行为和控制状态的最终转换。控制状态转换时通过调用执行器函数的两个参数 resolve 和 reject 实现。

let p1 = new Promise((resolve, reject) => resolve())
setTimeout(console.log, 0, p1) // Promise <resolved>
let p2 = new Promise((resolve, reject) => reject())
setTimeout(console.log, 0, p2) // Promise <rejected>

可以使用 Promise.resolve 和 Promise.reject 实例化一个兑现或拒绝的期约实例。

期约的实例方法

期约的实例方法是连接外部同步代码和内部异步代码之间的桥梁。 Promise.prototype.then 是为期约添加处理程序的主要方法。接收两个参数:onResolved 处理程序和 onRejected 处理程序,这两个参数都是可选的,如果提供则会在期约分别进入兑现和拒绝状态时执行。

let p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000))
let p2 = new Promise((resolve, reject) => setTimeout(reject, 3000))

p1.then(() => console.log('resolved'))
p2.then(null, () => console.log('rejected'))

调用 then 方法会创建一个新的期约实例,基于 onResolved 处理程序的返回值构建。如果没有处理程序则包装上一个期约解决后的值,如果没有显式的返回语句则包装默认的返回值 undefined 。 Promise.prototype.catch 用于给期约添加拒绝处理程序,这个方法实际上是 Promise.prototype.then(null, onRejected) 的语法糖。 Promise.prototype.finally 同于给期约添加 onFinally 处理程序,在期约切换为兑现或拒绝状态时都会执行。 当期约进入落定状态时,与该状态相关的处理程序仅仅会被排期,而非立即执行。跟在添加这个处理程序之后的同步代码一定会在处理程序之前先执行。这个特性称为非重入(non-reentrancy)

let p = Promise.resolve()
p.then(() => console.log('onResolved handler'))
console.log('then() returns')
// 输出
// then() returns
// onResolved handler

如果给期约添加了多个处理程序,当期约状态变化时,相关处理程序会按照添加它们的顺序依次执行。 到落定状态后,期约会提供值或理由给相关状态的处理程序。在执行函数中,值和理由分别作为 resolve 和 reject 的第一个参数向后传递。在处理程序中,作为 onResolved 和 onRejected 处理程序的唯一参数接收。

期约连锁与期约合成

期约连锁指把期约逐个地串联起来,是一种非常有用的编程模式。之所以可以实现是因为每个期约实例的方法都会返回一个新的期约对象。 Promise 类提供将多个期约实例合成一个期约的静态方法:Promise.all 和 Promise.race 。

异步函数

async 关键字用于声明异步函数,如果异步函数使用 return 关键字返回了值,这个值会被 Promise.resolve 包装成一个期约对象。 await 关键字必须在异步函数中使用,用于暂停异步函数代码的执行,等待期约解决。JavaScript 运行时在碰到 await 关键字时,会记录在哪里暂停执行。等到 await 右边的值可用,JavaScript 运行时会向消息队列中推送一个任务恢复异步函数的执行。因此,即使 await 后面跟着一个立即可用的值,函数的其余部分也会被异步求值。

async function foo() {
  console.log(2)
  await null
  console.log(4)
}
console.log(1)
foo()
console.log(3)

// 1
// 2
// 3
// 4

使用异步函数实现 sleep :

async function sleep(delay) {
  return new Promise((resolve) => setTimeout(resolve, delay))
}
相关文章