【面试题】面试官:你能自己实现一个async await吗?

简介: 【面试题】面试官:你能自己实现一个async await吗?

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

前言

首先我们要知道以下几个相关的知识点:

  1. async 函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。
  2. 当我们使用 async 声明了一个异步函数,那么我们就可以在这个异步函数内部使用 await 关键字。
  3. await 关键字只能接在async函数中使用,不能单独出现。
  4. async函数 返回的是一个状态为'fulfilled'Promise对象,如果有 return xxx;那就相当于resolve(xxx);如果没有return,则返回Promise {<fulfilled>: undefined}Promise对象。
  5. await关键字后面最好接的是Promise对象,以便于更好的实现异步(并不意味着不能接非Promise对象)。

官方的async await

function request(num) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * 10);
    }, 1000);
  });
}
async function fn() {
  const res1 = await request(1)
  const res2 = await request(res1)
  console.log(res2);
}
console.log(fn());
复制代码

将上一个await的结果作为第二个await的参数,最后将第二个await的结果打印出来。

其实明白Promise.then链式调用的伙伴就知道,这个效果使用.then的链式调用就可以实现。当然在.then里面拿到resolve出来的结果再给到下一个.then作为参数,以此往复就可以实现,但是在链式调用传参时,很容易把自己绕晕,而且我如果写了很多个await那就会嵌套很深,最后的代码会是一大坨,不美观。

那如果我不这样链式使用,我该如何等上一个Promise对象的执行结果出来之后,把结果作为第二个Promise对象的参数呢,这个时候我们就需要知道在ES6中提供了一种为异步编程解决方案的函数Generator函数,它是我们实现async await的主角之一,在这我不过多介绍,直接带大家迅速了解他它的用法,我们在实现async await就是使用了它的简单用法。

Generator函数

Generator 函数是一个状态机,封装了多个内部状态。 执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号(*);二是,函数体内部使用yield表达式,定义不同的内部状态。

function* gen() {
  yield 1;
  yield 2;
  yield 3;
  return 4;
}
const g = gen()
console.log(g);
复制代码

我们直接打印这个函数执行结果看看是什么样子?

诶?怎么就是一个编译好的gen函数呢,那个return 4呢? **Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。**

那如果我在调用next方法的时候传了一个参数,那这个参数会给到谁呢?

诶?为啥好像没啥变化呀?先别急,我换种写法你再看看

原来在next里面的参数给到了上一次yield的执行结果,并且在yield都执行完了之后内部的状态为true意味着,这个gen函数已经执行完了。

既然await 后面建议接Promise对象,那我就把yield 后面的换成Promise对象不就好了吗?这样我不就能控制在上一个Promise对象执行结束之后拿到了结果,再使用next传参不就是我们最开始想要的效果吗?

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

只要这个Promise对象的状态变更为'fulfilled',那我就在它的.then里面传参不就好了

generatorToaAsync()

有以上这些基础之后我们来使用一个高阶函数来实现async await的功能,因为我们不能呢声明像await一样的关键字

function* gen() {
  ...
}
function generatorToaAsync(generatorFn){
    // ...
    return // 具有async函数功能的函数
}
const asyncFn = generatorToaAsync(gen);
console.log(asyncFn());  // Promise{}
复制代码

我们使用高阶函数声明一个函数,这个函数接受一个参数(generator函数),返回出一个具有async功能的函数

// 模拟async
function generatorToaAsync(generatorFn) {
  // ...
  //具有async功能的函数
  return function () {
    return new Promise((resolve, reject) => {
    })
  }
}
复制代码

这样万里长征的第一步,返回了一个具有async功能的Promise对象。

结合一下我们上面generator的部分例子

function foo(num) {
  // console.log(num);
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(num * 2);
    }, 1000);
  })
}
function* gen() {
  const num1 = yield foo(1);
  const num2 = yield foo(num1);
  const num3 = yield foo(num2);
  return num3;
}
// 模拟async
function generatorToaAsync(generatorFn) {
  // ...
  //具有async功能的函数
  return function () {
    const gen = generatorFn()  // 相当于前面加载好整个generator函数,,方面后面.next()去一层一层执行
    return new Promise((resolve, reject) => {
      const next1 = gen.next()
      next1.value.then(res1 => {
        const next2 = gen.next(res1)
        next2.value.then(res2 => {
          const next3 = gen.next(res2)
          next3.value.then(res3 => {
            resolve(gen.next(res3).value)
          })
        })
      })
    })
  }
}
const asyncFn = generatorToaAsync(gen);
// console.log(asyncFn());  // Promise{}
asyncFn().then(res => {
  console.log(res);
})
复制代码

我们借助了generator函数next方法,改变其gen函数内部yield的状态,最终我们将gen函数return 的值再通过我们返回的Promise对象resolve出来,让我们看一下效果;

让我们看一眼官方async await实现的效果

是不是和我们实现的效果一致,最后我们只需要优化改进一下,就实现了async函数的效果;因为上面我们是站在上帝视角,知道gen函数里面有几个yield这样我们就是调用了三次.then就可以resolve出来。

首先我们考虑到this的指向问题,我们先将作为参数的(generator函数的this绑定到我们的定义的这个高阶函数generatorToaAsync身上),再将gen函数中执行的Promise函数全部作为参数给到generatorToaAsync

const gen = generatorFn.apply(this, arguments)
复制代码

既然不知道我们要调用几次.then,那么我们就是定义一个函数来判断Promise的状态,在函数内部使用递归调用

function loop(key,arg) {
        let res = null;
        res = gen[key](arg); // == gen.next(arg)  // { value: Promise { <pending> }, done: false }
        const { value, done } = res;
        if(done) {
          return resolve(value);
        }else {   // 没执行完yield
          // Promise.resolve(value) 为了保证value 中 Promise状态已经变更成'fulfilled'
          Promise.resolve(value).then(val => loop('next',val));
        }
      }
      loop('next')
复制代码

首先我们执行一个这个函数,将'next'用字符串作为参数,在函数内部使用变量来调用next方法,每一次循环拿到的都是 { value: Promise { <pending> }, done: false }根据done的状态判断是继续递归,还是resolve出结果在函数内部声明一个res 存放 每一次的状态和值。这样就实现了最终版的。

function foo(num) {
  // console.log(num);
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(num * 2);
    }, 1000);
  })
}
function* gen() {
  const num1 = yield foo(1);
  const num2 = yield foo(num1);
  const num3 = yield foo(num2);
  return num3;
}
// 模拟async
function generatorToaAsync(generatorFn) {
  // ...
  //具有async功能的函数
  return function () {
    const gen = generatorFn.apply(this, arguments)
    // const gen = generatorFn()
    return new Promise((resolve, reject) => {
      function loop(key,arg) {
        let res = null;
        res = gen[key](arg); // 等价于gen.next(arg)  // { value: Promise { <pending> }, done: false }
        const { value, done } = res;
        if(done) {
          return resolve(value);
        }else {   // 没执行完yield
          // Promise.resolve(value) 为了保证value 中 Promise状态已经变更成'fulfilled'
          Promise.resolve(value).then(val => loop('next',val));
        }
      }
      loop('next')
    })
  }
}
const asyncFn = generatorToaAsync(gen);
// console.log(asyncFn());  // Promise{}
asyncFn().then(res => {
  console.log(res);
复制代码

写在最后

虽然我们不能使用await关键字,但是我们可以使用generator函数里面的yield,然后将整个generator函数作为参数传递给我们定义的高阶函数,就能实现官方async await 一样的效果了。

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

相关文章
|
6月前
|
算法 Java 调度
《面试专题-----经典高频面试题收集四》解锁 Java 面试的关键:深度解析并发编程进阶篇高频经典面试题(第四篇)
《面试专题-----经典高频面试题收集四》解锁 Java 面试的关键:深度解析并发编程进阶篇高频经典面试题(第四篇)
74 0
|
2月前
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
35 0
|
6月前
|
存储 Java
java面试题大全带答案_面试题库_java面试宝典2018
java面试题大全带答案_面试题库_java面试宝典2018
|
6月前
|
存储 设计模式 Java
java实习生面试题_java基础面试_java面试题2018及答案_java面试题库
java实习生面试题_java基础面试_java面试题2018及答案_java面试题库
|
6月前
|
SQL 算法 安全
java面试宝典_java基础面试_2018java面试题_2019java最新面试题
java面试宝典_java基础面试_2018java面试题_2019java最新面试题
|
5月前
|
存储 安全 Java
Java面试题:Java内存管理、多线程与并发框架:一道综合性面试题的深度解析,描述Java内存模型,并解释如何在应用中优化内存使用,阐述Java多线程的创建和管理方式,并讨论线程安全问题
Java面试题:Java内存管理、多线程与并发框架:一道综合性面试题的深度解析,描述Java内存模型,并解释如何在应用中优化内存使用,阐述Java多线程的创建和管理方式,并讨论线程安全问题
45 0
|
5月前
|
存储 并行计算 安全
Java面试题:Java内存管理、多线程与并发框架的面试题解析与知识点梳理,深入Java内存模型与垃圾回收机制,Java多线程机制与线程安全,Java并发工具包与框架的应用
Java面试题:Java内存管理、多线程与并发框架的面试题解析与知识点梳理,深入Java内存模型与垃圾回收机制,Java多线程机制与线程安全,Java并发工具包与框架的应用
82 0
|
6月前
|
XML Java 数据库连接
面试必备!Java核心技术100+面试题
面试必备!Java核心技术100+面试题
|
6月前
|
安全 Java 中间件
《面试专题-----经典高频面试题收集一》解锁 Java 面试的关键:深度解析常见高频经典面试题(第一篇)
《面试专题-----经典高频面试题收集一》解锁 Java 面试的关键:深度解析常见高频经典面试题(第一篇)
51 0
|
6月前
|
安全 Java API
《面试专题-----经典高频面试题收集三》解锁 Java 面试的关键:深度解析并发编程基础篇高频经典面试题(第三篇)
《面试专题-----经典高频面试题收集三》解锁 Java 面试的关键:深度解析并发编程基础篇高频经典面试题(第三篇)
43 0