JavaScript异步函数

异步,在计算机编程中,是指独立于主程序流程的事件发生以及处理此类事件的方式。

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

异步生成器函数结合了异步函数和生成器函数的功能。可以在函数体内使用 awaityield 关键字。这使得能够使用 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;
相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax