
异步,在计算机编程中,是指独立于主程序流程的事件发生以及处理此类事件的方式。
JavaScript 语言的执行环境是"单线程"的,异步编程对 JavaScript 语言十分重要。传统的异步解决方案主要有事件驱动和回调函数,ECMAScript 2015引入 Promise 对象。它最早由社区提出,并形成 Promise/A+ 规范,后来成为 ECMAScript 标准的一部分。
Promise 对象
Promise代表异步操作的最终结果,与Promise交互的主要方式是通过其then方法,该方法注册回调以接收Promise的最终值或Promise无法实现的原因。该规范详细说明了then方法的行为,提供了所有Promises/A+一致Promise实现都可以依赖的可互操作基础。
根据规范,promise 是一个具有 then 方法的对象或函数,thenable 是定义 then 方法的对象或函数。可以理解为,对象实现了 then 方法,就表示它是 promise 对象。
Promise 对象有以下两个特点:
- 对象的状态不受外界影响。
Promise对象有有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 - 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:pending变为fulfilled,或者pending变为rejected。
有了 Promise 对象,可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调地狱。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。不过,Promise 也有一些缺点。
- 无法取消
Promise,一旦新建它就会立即执行,无法中途取消。 - 如果不如果不设置回调函数,
Promise内部抛出的错误,不会反应到外部。 - 当处于
pending状态时,无法得知目前进展到哪一个阶段。
生成器函数
Generator 函数是 ECMAScript 2015 提供的一种异步编程解决方案,语法行为与传统函数完全不同。Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
声明
function* 声明创建新生成器函数与给定名称的绑定。生成器函数可以退出并稍后重新进入,其上下文(变量绑定)在重新进入时保存。
js
function* generator(n) {
yield n
yield n + 2
return n + 3
}
const gen = generator(1)
function* 声明创建一个 GeneratorFunction 对象,调用一个生成器函数并不会马上执行它里面的语句。每次调用生成器函数时,它都会返回一个新的 Generator 对象,该对象符合迭代器(iterator)协议。只有调用 next 方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。
js
console.log(gen.next()) // {value: 1, done: false}
console.log(gen.next()) // {value: 2, done: false}
当调用迭代器的 next() 方法时,将执行生成器函数的主体,直到第一个 yield 表达式,该表达式指定要从迭代器返回的值,或者使用 yield* 方法返回一个对象,该对象具有包含生成值的 value 属性和 done 属性,该属性指示生成器是否已生成其最后一个值(作为布尔值)。如果是 yield*,则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
js
function* foo(n) {
yield n * n
yield* generator(x)
let x = yield n + 10
yield n * n * n
}
const f = foo(2)
console.log(f.next()) // {value: 4, done: false}
console.log(f.next()) // {value: 2, done: false}
使用参数调用 next() 方法将恢复生成器函数的执行,并用 next() 中的参数替换暂停执行的 yield 表达式。生成器中的 return 语句在执行时将使生成器完成(即它返回的对象的 done 属性将设置为 true)。如果返回一个值,它将被设置为生成器返回的对象的 value 属性。
js
console.log(f.next(10)) // {value: 3, done: false}
console.log(f.next()) // {value: 12, done: false}
console.log(f.next()) // {value: 8, done: false}
console.log(f.next()) // {value: undefined, done: true}
与 return 语句非常相似,生成器内部抛出的错误将使生成器完成,除非在生成器体内捕获错误。当生成器完成时,后续的 next() 调用将不会执行该生成器的任何代码,它们只会返回以下形式的对象:{value: undefined, done: true}。value 属性表示 yield 表达式的返回值,done 属性为布尔类型,表示生成器后续是否还有 yield 语句,即生成器函数是否已经执行完毕。
- 遇到
yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。 - 下一次调用
next方法时,再继续往下执行,直到遇到下一个yield表达式。 - 如果没有再遇到新的
yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。 - 如果该函数没有
return语句,则返回的对象的value属性值为undefined。
function* 声明的行为与 function 声明类似:它们被提升到其作用域的顶部,可以在其作用域中的任何位置调用,并且只能在某些上下文中重新声明。
js
function* gen() {
yield 1
yield 2
yield 3
}
const g = gen()
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
next()、throw()、return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。
js
function* gen () {
yield 'a'
yield 'b'
yield 'c'
}
console.log(g.next()) // { value: 1, done: false }
console.log(g.return('foo')) // { value: 'foo', done: true }
try {
g.throw(1)
} catch (e) {
console.log(e) // 1
}
表达式
function* 表达式与 function* 声明非常相似,并且具有几乎相同的语法。 function* 表达式和 function* 声明之间的主要区别在于函数名称,在 function* 表达式中可以省略函数名称以创建匿名函数。 function* 表达式可以用作 IIFE,它在定义后立即运行,从而允许创建临时可迭代迭代器对象。
js
const gen = function* () {
yield 'a'
yield 'b'
yield 'c'
}
构造函数
GeneratorFunction() 构造函数创建 GeneratorFunction 对象,不适合直接使用。请注意, GeneratorFunction 不是全局对象,可以通过以下代码获取:
js
const GeneratorFunction = function* () {}.constructor
异步函数
ECMAScript 2017 标准引入了 async 函数,使得异步操作变得更加方便。async 函数是什么?一句话,它就是 Generator 函数的语法糖。
声明
async function 声明创建新异步函数与给定名称的绑定。函数体内允许使用 await 关键字,从而能够以更简洁的风格编写基于 Promise 的异步行为,并避免显式配置 Promise 链的需要。
js
async function fn () {
return 1
}
console.log(fn()) // Promise {<fulfilled>: 1}
async function 声明创建一个 AsyncFunction 对象。每次调用 async 函数时,它都会返回一个新的 Promise,该新 Promise 将使用 async 函数返回的值进行解析,或者因 async 函数内未捕获的异常而被拒绝。async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。
async 函数可能包含 0 个或者多个 await 表达式。await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。promise 的解决值会被当作该 await 表达式的返回值。使用 async/await 关键字就可以在异步代码中使用普通的 try/catch 代码块。
async 函数的主体可以被认为是被 0 个或多个 await 表达式分割。顶级代码,直到并包括第一个等待表达式(如果有),都是同步运行的。这样,没有 await 表达式的async 函数将同步运行。但是,如果函数体内有 await 表达式,则async 函数将始终异步完成。
每个 await 表达式之后的代码可以被认为存在于 .then 回调中。通过这种方式,通过函数的每个可重入步骤逐步构建 promise 链。返回值构成了链中的最后一个环节。请注意,promise 链并不是一次性建立起来的。相反,promise是分阶段构建的,控制权依次从 await 函数中产生并返回给 await 函数。因此,在处理并发异步操作时,我们必须注意错误处理行为。
js
function timer(v) {
return new Promise((resolve, reject) => {
setTimeout(() => {
v > 0.5 ? resolve('hello') : reject('world')
}, 1000)
})
}
async function fn() {
try {
const v1 = await timer(1)
console.log(v1) // hello
const v2 = await timer(0.1)
console.log(v2)
} catch (e) {
console.log(e) // world
}
}
fn()
async function 声明的行为与 function 声明类似:它们被提升到其作用域的顶部,可以在其作用域中的任何位置调用,并且只能在某些上下文中重新声明。
表达式
async function 表达式与 async function 声明非常相似,并且具有几乎相同的语法。 async function 表达式和 async function 声明之间的主要区别在于函数名称,在 async function 表达式中可以省略函数名称以创建匿名函数。 async function 表达式可以用作 IIFE(立即调用函数表达式),它在定义后立即运行,允许模仿顶级 await。
js
(async function (x) {
return await timer(x)
})(10).then((v) => {
console.log(v) // hello
})
构造函数
AsyncFunction() 构造函数创建 AsyncFunction 对象,不适合直接使用。请注意, AsyncFunction 不是全局对象。可以通过以下代码获取:
js
const AsyncFunction = async function () {}.constructor
异步生成器函数
就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。在语法上,异步 Generator 函数就是 async 函数与 Generator 函数的结合。
声明
async function* 声明创建新的异步生成器函数与给定名称的绑定。
js
async function* foo() {
yield await Promise.resolve('a')
yield await Promise.resolve('b')
yield await Promise.resolve('c')
}
let str = ''
async function generate() {
for await (const val of foo()) {
str = str + val
}
console.log(str)
}
generate() // 'abc'
async function* 声明创建一个 AsyncGeneratorFunction 对象。每次调用异步生成器函数时,它都会返回一个新的 AsyncGenerator 对象,该对象符合异步迭代器协议。每次调用 next() 都会返回一个解析为迭代器结果对象的 Promise 。
异步生成器函数结合了异步函数和生成器函数的功能。可以在函数体内使用 await 和 yield 关键字。这使得能够使用 await 更方便地处理异步任务,同时利用生成器函数的惰性本质。
当从异步生成器生成 Promise 时,迭代器结果 Promise 的最终状态将与生成的 Promise 的状态相匹配。
js
async function* foo() {
yield Promise.reject(1)
}
foo().next().catch(console.error)
async function* 声明的行为与 function 声明类似: 它们被提升到其作用域的顶部,可以在其作用域中的任何位置调用,并且只能在某些上下文中重新声明。
表达式
async function* 表达式与 async function* 声明非常相似,并且具有几乎相同的语法。 async function* 表达式和 async function* 声明之间的主要区别在于函数名称,在 async function* 表达式中可以省略函数名称以创建匿名函数。 async function* 表达式可以用作 IIFE(立即调用函数表达式),它在定义后立即运行,允许您创建临时异步可迭代对象。
js
const x = async function* (y) {
yield Promise.resolve(y * y)
}
x(6).next().then((res) => console.log(res.value)) // 36
构造函数
AsyncGeneratorFunction() 构造函数创建 AsyncGeneratorFunction 对象,不适合直接使用。请注意, AsyncGeneratorFunction 不是全局对象。它可以通过以下代码来获取:
js
const AsyncGeneratorFunction = async function* () {}.constructor;