【面试题】手写async await核心原理,再也不怕面试官问我async await原理

简介: 【面试题】手写async await核心原理,再也不怕面试官问我async await原理

前言

async await 语法是 ES7出现的,是基于ES6的 promise和generator实现的

generator函数

在之前我专门讲个generator的使用与原理实现,大家没了解过的可以先看那个手写generator核心原理,再也不怕面试官问我generator原理

这里就不再赘述generator,专门的文章讲专门的内容。

await在等待什么

我们先看看下面这代码,这是async await的最简单使用,await后面返回的是一个Promise对象:

async function getResult() {
    await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
            console.log(1);
        }, 1000);
    })
}
getResult()

但不知你有没有想过一个问题,为什么会等到返回的promise的对象的状态为非pending的时候才会继续往下执行,也就是resolve执行之后,才会继续执行,就像下面的代码一样

async function getResult() {
    await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
            console.log(1);
        }, 1000);
    })
    console.log(2);
}
getResult()

可以看到运行结果是先打印了1,再打印2了,也就是说明在返回的promise对象没执行resolve()前,就一直在await,等它执行。然后再执行下面的程序,那这个是怎么实现的呢?

原理实现

我们看一下下面的代码,输出顺序是什么?

async function getResult() {
    await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
            console.log(1);
        }, 1000);
    })
    await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2);
            console.log(2);
        }, 500);
    })
    await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(3);
            console.log(3);
        }, 100);
    })
}
getResult()

没错是 1,2,3.

那用generator函数专门来实现这个效果呢

我一开始这样来实现:

function* getResult(params) {
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
            console.log(1);
        }, 1000);
    })
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2);
            console.log(2);
        }, 500);
    })
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(3);
            console.log(3);
        }, 100);
    })
}
const gen = getResult()
gen.next();
gen.next();
gen.next();

但是发现打印顺序是 3,2,1.明显不对。

这里的问题主要是三个 new Promise几乎是同一时刻执行了。才会出现这种问题,所以需要等第一个promise执行完resolve之再执行下一个,所以要这么实现

function* getResult(params) {
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
            console.log(1);
        }, 1000);
    })
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2);
            console.log(2);
        }, 500);
    })
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(3);
            console.log(3);
        }, 100);
    })
}
const gen = getResult()
gen.next().value.then(() => {
    gen.next().value.then(() => {
        gen.next();
    });
});

可以看到这样就打印正常了。

特别 需要解释下。gen.next().value 就是返回的promise对象,不理解的可以看看文首介绍的那篇generator的 文章。手写generator核心原理,再也不怕面试官问我generator原理

优化

但是呢,总不能有多少个await,就要自己写多少个嵌套吧,所以还是需要封装一个函数,显然,递归实现最简单

function* getResult(params) {
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
            console.log(1);
        }, 1000);
    })
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2);
            console.log(2);
        }, 500);
    })
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(3);
            console.log(3);
        }, 100);
    })
}
const gen = getResult()
function co(g) {
    g.next().value.then(()=>{
        co(g)
    })
}
co(gen)

再来看看打印结果

可以发现成功执行了,但是为什么报错了?

这是因为generator方法会返回四次,最后一次的value是undefined。

而实际上返回第三次就表示已经返回done,代表结束了,所以,我们需要判断是否是已经done了,不再让它继续递归

所以可以改成这样

function* getResult(params) {
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
            console.log(1);
        }, 1000);
    })
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2);
            console.log(2);
        }, 500);
    })
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(3);
            console.log(3);
        }, 100);
    })
}
const gen = getResult()
function co(g) {
    const nextObj = g.next();
    if (nextObj.done) {
        return;
    }
    nextObj.value.then(()=>{
        co(g)
    })
}
co(gen)

可以看到这样就实现了。

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

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

地址:前端面试题库

2、前端技术导航大全      推荐:★★★★★

地址:前端技术导航大全

3、开发者颜色值转换工具   推荐:★★★★★

地址 :开发者颜色值转换工具

相关文章
|
3月前
|
消息中间件 存储 缓存
大厂面试高频:Kafka 工作原理 ( 详细图解 )
本文详细解析了 Kafka 的核心架构和实现原理,消息中间件是亿级互联网架构的基石,大厂面试高频,非常重要,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:Kafka 工作原理 ( 详细图解 )
|
2月前
|
存储 SQL 关系型数据库
MySQL进阶突击系列(03) MySQL架构原理solo九魂17环连问 | 给大厂面试官的一封信
本文介绍了MySQL架构原理、存储引擎和索引的相关知识点,涵盖查询和更新SQL的执行过程、MySQL各组件的作用、存储引擎的类型及特性、索引的建立和使用原则,以及二叉树、平衡二叉树和B树的区别。通过这些内容,帮助读者深入了解MySQL的工作机制,提高数据库管理和优化能力。
|
4天前
|
存储 NoSQL 前端开发
美团面试:手机扫描PC二维码登录,底层原理和完整流程是什么?
45岁老架构师尼恩详细梳理了手机扫码登录的完整流程,帮助大家在面试中脱颖而出。该过程分为三个阶段:待扫描阶段、已扫描待确认阶段和已确认阶段。更多技术圣经系列PDF及详细内容,请关注【技术自由圈】获取。
|
1月前
|
Java Linux 调度
硬核揭秘:线程与进程的底层原理,面试高分必备!
嘿,大家好!我是小米,29岁的技术爱好者。今天来聊聊线程和进程的区别。进程是操作系统中运行的程序实例,有独立内存空间;线程是进程内的最小执行单元,共享内存。创建进程开销大但更安全,线程轻量高效但易引发数据竞争。面试时可强调:进程是资源分配单位,线程是CPU调度单位。根据不同场景选择合适的并发模型,如高并发用线程池。希望这篇文章能帮你更好地理解并回答面试中的相关问题,祝你早日拿下心仪的offer!
38 6
|
2月前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
3月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
3月前
|
安全 算法 网络协议
网易面试:说说 HTTPS 原理?HTTPS 如何保证 数据安全?
45岁老架构师尼恩在其读者交流群中分享了关于HTTP与HTTPS的深入解析,特别针对近期面试中常问的HTTPS相关问题进行了详细解答。文章首先回顾了HTTP的工作原理,指出了HTTP明文传输带来的三大风险:窃听、篡改和冒充。随后介绍了HTTPS如何通过结合非对称加密和对称加密来解决这些问题,确保数据传输的安全性。尼恩还详细解释了HTTPS的握手过程,包括如何通过CA数字证书验证服务器身份,防止中间人攻击。最后,尼恩强调了掌握这些核心技术的重要性,并推荐了自己的技术资料,帮助读者更好地准备面试,提高技术水平。
|
6月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
3月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
3月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
96 4

热门文章

最新文章