前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

这篇手写async函数及原理|博客系列

# 正文

ES8推出的async/await允许我们用更加优雅的方式来实现异步代码,从某种角度上来说,它是Generator函数的语法糖,就像我们经常说class是构造函数的语法糖一样。

# async案例

基本用法啥的我就不说了,这里直接上一个简单的案例,后面一步步的转换过程都是以此案例作为基础。

const getData = () => new Promise(resolve => setTimeout(() => { resolve('data') }, 1000))

async function test () {
  const data = await getData();
  console.log('data: ', data);
  const data2 = await getData();
  console.log('data2: ', data2);
  return 'success';
}
test().then(res => console.log(res));
@前端进阶之旅: 代码已经复制到剪贴板

针对于上面的这段代码,相信大家都没有什么疑问,很快我们就能说出答案:

// 1s 后打印出
'data ' 'data'
// 1s 后打印出
'data2: ' 'data'
'success'
@前端进阶之旅: 代码已经复制到剪贴板

# Generator案例

让我们先来回顾一下Generator最基本的一些概念:

  • function后面加上*表示这是一个Generator函数,如function* testG(){}
  • 函数内部可以使用yield来中断函数的执行,即当每次执行到yield语句的时候,函数暂停执行
  • 暂停执行之后,需要调用next()才会继续执行Generator函数,直到碰到函数内下一个yield又会暂停
  • 以此循环,直到函数内有return或者函数内代码全部执行完

其中还有很重要的一个知识点,就是每次调用next()的返回值,它是一个对象,这个对象会有两个属性:

  • value:yield语句后的表达式的结果
  • done:当前的Generator对象的逻辑块是否执行完成

如果说我们把上面的案例转换为Generator来实现的话,我们想的可能会是这样来写:

案例一:

const getData = () => new Promise(resolve => setTimeout(() => { resolve('data') }, 1000))

function* testG () {
  const data = yield getData();
  console.log('data: ', data);
  const data2 = yield getData();
  console.log('data2: ', data2);
  return 'success';
}
var gen = testG();
// 之后手动调用3次
gen.next();
gen.next();
gen.next();
@前端进阶之旅: 代码已经复制到剪贴板

可是上面👆的代码真的会和async案例中的执行结果一样吗?

当我打开控制台的时候,结果却出乎我的意料:

data: undefined
data2: undefined
@前端进阶之旅: 代码已经复制到剪贴板

What…?

你的suceess没有打印就算了,你的data和data2竟然都还是undefined?这就有点难以理解了。

难道说是我的调用姿势不对吗…

本着良好的职业素养,我对Generator研究了一波,然后修改了一下上面的代码:

案例二:

const getData = () => new Promise(resolve => setTimeout(() => { resolve('data') }, 1000))

function* testG () {
  const data = yield getData();
  console.log('data: ', data);
  const data2 = yield getData();
  console.log('data2: ', data2);
  return 'success';
}
var gen = testG();
// 手动调用3次且把每次的返回值打印出来看看
var dataPromise= gen.next();
console.log(dataPromise);
var dataPromise2 = gen.next('这个参数才会被赋给data变量');
console.log(dataPromise2);
var dataPromise3 = gen.next('这个参数才会被赋给data2变量');
console.log(dataPromise3);
@前端进阶之旅: 代码已经复制到剪贴板

可以看到不同之处在于我把调用三次的返回值用了一个变量来盛放,并且在后面两次调用gen.next()的时候传递了参数进去。

现在的输出结果为:

{value: Promise, done: false}
'data:', '这个参数才会被赋给data变量'
{value: Promise, done: false}
'data2:', '这个参数才会被赋给data2变量'
{value: Promise, done: false}
@前端进阶之旅: 代码已经复制到剪贴板

What X 2…?

哈哈哈,是不是有点摸不着头脑了,这data和data2的返回值难道不是yield getData()的结果吗?

我这里的代码明明就是const data = yield getData()呀,可是怎么会变成了'这个参数才会被赋给data变量'呢?

原来有些的你以为并不是真的你以为,这里详细把每一步都分析一下:

OK👌,相信聪明的你现在一定弄懂Generator的执行机制了,它和我们的async函数是有一些区别的。

在async函数中,const data = await getData(),这个data是getData()中resolve()的结果。

而generator函数中,const data = yield getData(),其实只执行了yield getData()函数而已并且会把这个值作为第一次调用gen.next()的返回值,也就是被dataPromise所接收。

data需要等到下一次调用gen.next()时才会被赋值,这也就是为什么我们在案例一中data会是undefined,因为那时候我们调用gen.next()是没有传递任何参数的。

# Generator案例转换async案例

显然,上面👆两个案例的执行结果和async案例中的结果是不一样的,我只是将其做了一些拆分以便让你更好的理解接下来我要做的事情。

嘻嘻😁,那么如何让案例二能打印出和async案例一样的结果呢?

细心的小伙伴可能已经有了一些想法,dataPromise中会有调用返回的Promise对象,那么我们也就能拿到这个Promise的返回值'data'了,只需要使用.then()来进行一个链式调用,就像下面这样:

const getData = () => new Promise(resolve => setTimeout(() => { resolve('data') }, 1000))

function* testG () {
  const data = yield getData();
  console.log('data: ', data);
  const data2 = yield getData();
  console.log('data2: ', data2);
  return 'success';
}
var gen = testG();
var dataPromise = gen.next();
dataPromise.value.then((value1) => {
  var dataPromise2 = gen.next(value1);
  dataPromise2.value.then((value2) => {
    var dataPromise3 = gen.next(value2)
    console.log(dataPromise3.value)
  })
})
@前端进阶之旅: 代码已经复制到剪贴板

现在的结果就是和async案例的结果一样咯:

(如果你对这道题的结果还是比较模糊的话请再仔细看一下我在案例二的那一大坨代码注释哦)

// 1s 后打印出
'data ' 'data'
// 1s 后打印出
'data2: ' 'data'
'success'
@前端进阶之旅: 代码已经复制到剪贴板

(注意⚠️,在每次调用gen.next()的时候,它的返回值是一个{ value: {}, done: false }这样的对象,所以我们想要使用返回的Promise的时候,需要用dataPromise.value来获取)

# throw()

在正式讲解之前,让我们再来认识一下Generator的另一个实例方法throw(),之所以说到它,是因为我们最终的代码需要用到它。

它和next()一样,都是属于Generator.prototype上的方法,且返回值也是和next()一样。

让我们来看一个简单的案例了解一下它是怎样使用的哈:

(利用while(true){}循环,我们创建了一个可以无限调用的Generator函数)

function* gen () {
  while (true) {
    try {
      yield 'LinDaiDai'
    } catch (e) {
      console.log(e)
    }
  }
}
var g = gen();
g.next(); // { value: 'LinDaiDai', done: false }
g.next(); // { value: 'LinDaiDai', done: false }
g.throw(new Error('错误')); // Error: '错误'
@前端进阶之旅: 代码已经复制到剪贴板

而且你会发现,并不是throw中一定要传一个new Error()才会被里面的catch捕获,你就算是传递一个别的类型的值进去,也会,例如我直接传递字符串'错误':

g.throw('错误'); // '错误'
@前端进阶之旅: 代码已经复制到剪贴板

它也会被捕获。

# Generator实现async?

我们在搞懂了Generator的执行机制之后,就可以来看看,async是怎样用Generator来实现的了。

首先,让我们确定一下我们要做的事情:

const getData = () => new Promise(resolve => setTimeout(() => { resolve('data') }, 1000))

function* testG () { // 这个就是上面的那个案例
  const data = yield getData();
  console.log('data: ', data);
  const data2 = yield getData();
  console.log('data2: ', data2);
  return 'success';
}

// 我们需要设计一个转换函数
function asyncToGenerator (genFunc) {}

var test = asyncToGenerator(testG);
test().then(res => console.log(res));
@前端进阶之旅: 代码已经复制到剪贴板

那么可以看到,现在的关键就是在于实现一个asyncToGenerator转换函数,它有以下特点:

  • 接收一个Generator函数
  • 返回一个Promise

而Generator你们也看到了,它是很懒的,需要我们每调用一次next()函数它才走一步,所以如何让它自动的执行成为了我们需要思考的点。

← 再来40道this面试题酸爽继续从206个console.log完全弄懂数据类型转换的前世今生上 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合