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;
相关推荐
J不A秃V头A27 分钟前
Vue3:编写一个插件(进阶)
前端·vue.js
司篂篂1 小时前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客1 小时前
pinia在vue3中的使用
前端·javascript·vue.js
宇文仲竹2 小时前
edge 插件 iframe 读取
前端·edge
Kika写代码2 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构
天下无贼!3 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr3 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林3 小时前
npm发布插件超级简单版
前端·npm·node.js
我码玄黄3 小时前
THREE.js:网页上的3D世界构建者
开发语言·javascript·3d
罔闻_spider3 小时前
爬虫----webpack
前端·爬虫·webpack