async和await是什么

近些年在前端项目中涉及到异步代码基本都能看到 async/await 的身影,他的最大的作用就是能够中断当前代码的运行,当 await 后面的 Promise 对象的状态变成 fulfilled 的时候才继续接着执行后面的代码。这就让之前一些传递回调函数的写法变成了同步的代码方式。那么 async/await 是怎么回事呢

回调式写法

一开始异步的写法全靠回调函数嵌套回调函数比如下面的代码

javascript 复制代码
$.ajax({
  success() {
    $.ajax({
      success() {}
    })
  }
})

在一些有一定年头的项目里,类似的代码不在少数,非常容易形成回调地狱的问题,代码嵌套层次深缩进大,导致结构混乱可读性降低。当然如果你能力好,把一些回调函数封装成独的函数,可能会提升一点代码质量,但是避免不了回调函数被传递来传递去,最后都不知道传递进去的函数会在什么时候被调用

Promise

随着的 Promise 的出现,回调地狱的问题有的缓解,因为它带来一种新的编程方式

javascript 复制代码
fetch(url).then(handle1).then(handle2).then(handle3)

上面的代码实现了在请求完成后,按顺序执行回调函数,每个回调函数执行完成后返回一个结果,这个结果会被传递给下一个回调函数

这就把多个回调函数按一定的顺序链起来了,当页面的回调函数成功执行并返回结果时才会触发下一个回调函数的执行,这样的结构是有顺序的,所以感觉上就更有条理了。然后我们代码并不是顺序执行的,这也是面试常考问题,给你一个函数,里面一堆 setTimeout Promise console 然后问你console 的输出顺序

async/await

这就再进一步了,把 promise 的这种异步回调的方式同步的方式来编写,进一步的优化代码结构,增强代码可读性

javascript 复制代码
async function main(url) {
  let res = await fetch(url)
  res = await handle1(res)
  res = handle2(res)
  return handle3(res)
}

这样在main方法内部将以同步的方式来执行代码,虽然 js 执行代码还是有异步处理,但是从代码上看,后面的处理函数一定是在前面的处理函数完成之后才会执行。

那 async/await 是什么呢,它其实就一种语法糖。是生成器 Generator 的语法糖,其实 async/await 最大的作用就是中断方法执行,然后在适当的时机恢复执行。而在 js 里这正是生成器的功能,那什么又是生成器呢,其实就是通过星号标记的函数

其实生成器早在2011年就有了

这是我 2012 买的 JavaScript 权威指南 里的一个截图,这本书当时翻译是生成器,和截图里上面的 let 语句一样,yield 语句也是1.7新增,当时只适用于Firefox2.0及更高版本浏览器。可能当时这些只是提案吧。语法还不稳定,当时定义 Generator 函数都不需要星号。而且还有 let 语句,如今 let 已经是作为声明变量的关键这了。当年看到这章节的时候也是一头雾水。完全不知道这个东西的应用场景。直到后来 async/await 的兴起

自行包装

既然 async/await 是语法糖,那能不能自己封装一个呢,当然是可以的。直接上代码

javascript 复制代码
function generatorToAsync(executor) {
  // 这是其实可以判断一下executor应该是一个可执行函数
  function handler(gen, res, resolve, reject) {
    if (res.done) {
      return resolve(res.value);
    }
    if (typeof res.value.then === "function") {
      res.value.then((r) => gen.next(r)).then((r) => handler(gen, r, resolve, reject), reject);
    } else {
      Promise.resolve(res.value).then((r) => handler(gen, r, resolve, reject));
    }
  }

  return function (...args) {
    const { promise, resolve, reject } = Promise.withResolvers();
    try {
      const gen = executor.apply(this, args);
      if (Object.prototype.toString.call(executor) !== "[object Generator]") {
        resolve(gen)
      } else {
        handler(gen, gen.next(), resolve, reject);
      }
    } catch (err) {
      reject(err);
    }
    return promise;
  };
}

代码中的 Promise.withResolvers 是 ECMAScript®2024 中引入的新功能,有不了解的可以参考Promise这个新api有点香

在使用时需要定义一个生成器函数

javascript 复制代码
function* asyncFunction(args) {
  console.log(args);
  const res1 = yield new Promise(resolve => setTimeout(resolve, 1000, "yield1"));
  console.log(res1);
  const res2 = yield new Promise(resolve => setTimeout(resolve, 1000, "yield2"));
  console.log(res2);
  throw "return 3";
}

generatorToAsync(asyncFunction)("args").then(console.log, console.error);

如此执行在 asyncFunction 内部将顺序间隔1s 输出 args, res1, res2。且不用关心何时调用生成器的 next 方法,包装方法中会在 promise 完成的时候自动调用next 触发生成器的继续执行。这也 async/await 要干的事


早在2011年,这本JavaScript权威指南中就已经讲了各种新技术了,生成器,数组的 forEach, filter, reduce 等方法,SSE, Storage, Worker等,然而我清晰的记得,当时还是项目中疯狂使用jQuery, 以及一个叫DWZ的富客端开源框架,这些新技术基本都没用上,当然可能最主要的原因就是当年要兼容 IE😅

相关推荐
正小安22 分钟前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch2 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光2 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   2 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   2 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web2 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常2 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇3 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr3 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho4 小时前
【TypeScript】知识点梳理(三)
前端·typescript