首页/文章/八股文

手写async await核心原理

2025-02-26
902 字约 4 分钟
...

前言

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)

 

可以看到这样就实现了。

完美,这个co其实也是大名鼎鼎的co函数的简单写法

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
八股文2026-08-20
Embedding 向量模型:从语义表示到相似度计算
前言 大模型「读懂」文字靠的是 token,但 token 之间只有离散的编号关系,模型并不知道「苹果」和「李子」在语义上很近。要让程序能「理解」两段文字的相似程度,必须先把文本映射成一个 高维向量 ,再用几何方法比较。这一步就是 Embedding。 本篇基于我本地 今天的真实代码,从语义表示讲到余弦相似度,并复盘几个踩过的真实坑。 一、为什么需要 Embedding 传统关键词检索是「字面匹配」: Embedding 做的是「语义匹...
面试
八股文2026-08-19
前端面试100题
前端面试 100 题 适用方向:中高级前端 / React / Vue / Next.js / Nuxt / TypeScript / 工程化 / 实时通信 / Electron / Node.js / AI 应用前端 使用方式:优先掌握“标准回答”,再练“面试官追问”,最后把“结合你的简历怎么答”组织成自己的项目故事。 说明 “结合你的简历怎么答”只使用你简历中已经出现的项目与技术事实。 “标准回答 / 追问”属于通用前端知识总结,用...
面试
八股文2026-08-19
深入理解 JavaScript 原型与原型链:从关系图到核心逻辑 - heshanwan - 博客园
在 JavaScript 世界里, 原型(Prototype)和原型链(Prototype Chain) 是理解对象继承、属性查找机制的基石。很多开发者初学时对它们 “又爱又恨”,这篇文章将结合经典关系图,用通俗易懂的方式拆解原型与原型链的核心逻辑,帮你彻底掌握这套机制!   一、先搞懂几个核心概念 在分析关系图前,先明确 JavaScript 中与原型相关的关键概念,避免后续混淆: 1\. 函数对象与普通对象 函数对象 :由...
面试
八股文2026-08-17
JavaScript 闭包
摘要 :闭包(Closure)是 函数与其词法环境(Lexical Environment)的组合 ——一个函数可以"记住"并访问它被创建时所处的外部作用域中的变量,即使它在该作用域之外被执行。一句话记忆: 函数打包了出生地的变量,走到哪都能用 。核心要点:① 内部函数引用外部变量即形成闭包;② 闭包实现变量私有化/数据封装/柯里化;③ 潜在风险是内存泄漏和循环引用;④ 避免泄漏需手动解除引用和及时移除监听器。 一、什么是闭包 1.1...
面试
八股文2026-08-17
this 指向与 call / apply / bind
第 1 段(10min)回顾与预习:为什么必须搞懂 this 1.1 先接昨日(闭包)的线 昨天你学了 闭包 。闭包解决的是"函数记住它定义时所在作用域里的变量"。 闭包回答的问题是: “这个函数能访问到哪些变量?” → 看定义位置(词法作用域)。 而今天的主角 回答的是一个完全不同的问题: “这个函数执行时,站在谁的角度说话?” → 看 调用位置 (谁调用的、怎么调用的)。 一句话概括两者的分工: 变量看 定义处 , 看 调用处 。这...
面试
八股文2026-08-17
事件循环与宏任务 / 微任务
第 1 段(10min)回顾与预习:接住昨天的线 1.1 先接昨日(原型链)的线 昨天你学了 对象怎么找属性 (沿原型链上溯)。今天换一个维度: 代码怎么排队执行 。 把前三天的知识串成一句话,你已经有了 JS 的三张地图: 1.2 先凭直觉回答 4 个小问题(不用对,先写下答案) 学完第 2、3、4 段再回来对照。第 1 段只做一件事:记住画面—— JS 单线程,但靠"排队 + 轮转"假装多线程 。 第 2 段(15min)知识块①:...
面试

评论

请登录后发表评论

去登录
加载评论中...

目录