异步,在计算机编程中,是指独立于主程序流程的事件发生以及处理此类事件的方式。
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*
声明创建新生成器函数与给定名称的绑定。生成器函数可以退出并稍后重新进入,其上下文(变量绑定)在重新进入时保存。
function* generator(n) {
yield n
yield n + 2
return n + 3
}
const gen = generator(1)
function*
声明创建一个 GeneratorFunction
对象,调用一个生成器函数并不会马上执行它里面的语句。每次调用生成器函数时,它都会返回一个新的 Generator
对象,该对象符合迭代器(iterator)协议。只有调用 next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。
console.log(gen.next()) // {value: 1, done: false}
console.log(gen.next()) // {value: 2, done: false}
当调用迭代器的 next()
方法时,将执行生成器函数的主体,直到第一个 yield
表达式,该表达式指定要从迭代器返回的值,或者使用 yield*
方法返回一个对象,该对象具有包含生成值的 value
属性和 done
属性,该属性指示生成器是否已生成其最后一个值(作为布尔值)。如果是 yield*
,则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
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
属性。
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
声明类似:它们被提升到其作用域的顶部,可以在其作用域中的任何位置调用,并且只能在某些上下文中重新声明。
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表达式。
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,它在定义后立即运行,从而允许创建临时可迭代迭代器对象。
const gen = function* () {
yield 'a'
yield 'b'
yield 'c'
}
构造函数
GeneratorFunction()
构造函数创建 GeneratorFunction
对象,不适合直接使用。请注意, GeneratorFunction
不是全局对象,可以通过以下代码获取:
const GeneratorFunction = function* () {}.constructor
异步函数
ECMAScript 2017 标准引入了 async
函数,使得异步操作变得更加方便。async
函数是什么?一句话,它就是 Generator
函数的语法糖。
声明
async function
声明创建新异步函数与给定名称的绑定。函数体内允许使用 await
关键字,从而能够以更简洁的风格编写基于 Promise
的异步行为,并避免显式配置 Promise
链的需要。
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
函数。因此,在处理并发异步操作时,我们必须注意错误处理行为。
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
。
(async function (x) {
return await timer(x)
})(10).then((v) => {
console.log(v) // hello
})
构造函数
AsyncFunction()
构造函数创建 AsyncFunction
对象,不适合直接使用。请注意, AsyncFunction
不是全局对象。可以通过以下代码获取:
const AsyncFunction = async function () {}.constructor
异步生成器函数
就像 Generator
函数返回一个同步遍历器对象一样,异步 Generator
函数的作用,是返回一个异步遍历器对象。在语法上,异步 Generator
函数就是 async
函数与 Generator
函数的结合。
声明
async function*
声明创建新的异步生成器函数与给定名称的绑定。
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
的状态相匹配。
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(立即调用函数表达式),它在定义后立即运行,允许您创建临时异步可迭代对象。
const x = async function* (y) {
yield Promise.resolve(y * y)
}
x(6).next().then((res) => console.log(res.value)) // 36
构造函数
AsyncGeneratorFunction()
构造函数创建 AsyncGeneratorFunction
对象,不适合直接使用。请注意, AsyncGeneratorFunction
不是全局对象。它可以通过以下代码来获取:
const AsyncGeneratorFunction = async function* () {}.constructor;