V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
wefgonujnopu
V2EX  ›  JavaScript

js 不使用 promise 完成一个 sleep 函数,必须支持 await 调用

  •  
  •   wefgonujnopu · 21 天前 · 8732 次点击
    有没有人会写的,看看 v 站有多少大神
    要求:
    await sleep(毫秒),让当前 async 函数进入等待,不能阻塞线程

    提示: 实现起来非常简单,只要几行,不能使用任何库,要兼容浏览器环境
    第 1 条附言  ·  21 天前

    很多人说这个没用,还嘲讽深入理解,没有解决麻烦和任何问题
    我举个最简单的例子,python协程的async await,代码如下:

    import asyncio
    
    
    async def sleep(seconds):
        await asyncio.sleep(seconds)
    
    
    async def main(num=0):
        # 并发执行多个异步任务
        print(num)
        await sleep(0.0001)
        await main(num + 1)
    
    
    asyncio.run(main())
    
    

    输出到1000就会堆栈溢出,而用js的promise实现一样的函数,却不会
    然而一旦把sleep去掉,就会立马堆栈溢出

    function sleep(time){
        return new Promise(resolve => setTimeout(resolve, time));
    }
    
    
    async function main(num=0){
        console.log(num)
        //去掉sleep会堆栈溢出
        await sleep(1);
        await main(num+1)
    }
    
    main()
    

    因为js中的await实际上等效于then,也就是下面的代码,每次循环都是新的任务提交到事件循环队列,所以不会堆栈累加:

    async function main(num=0){
        console.log(num)
     
        sleep(1).then(()=>{
            main(num+1)  
        })
    }
    

    导致函数堆栈溢出的因素都可以说成不重要。没有任何意义,到底是自己没有认真学,还是真的不重要,各位自行判断吧

    第 2 条附言  ·  20 天前

    不要在说有什么用,有什么应用场景了,手写apply call bind这些面试题有应用场景吗?
    单纯为了兴趣研究下,就跟普通promise输出顺序的面试题一样,也可以写个类似的

    async function main() {
        await {
            then(func) {
                console.log(1)
                setTimeout(func,0);
                console.log(2)
            }
        }
        console.log(3)
    }
    
    main()
    
    ``
    148 条回复    2025-06-27 10:46:47 +08:00
    1  2  
    xz410236056
        101
    xz410236056  
       20 天前
    @wefgonujnopu #15 你这言论让我想起前几天看的一篇文章,用 C 语言搞一堆优化魔法为什么没 RUST 快,因为 C 语言没有跟进 CPU 的分支预测、simd 等特性。意思是,你搞这玩意(在很多人看来)毫无意义,真喜欢研究就去给 ECMAScript 提交标准呀(你所谓的那些实际执行什么的,完全不如一个新的研究、新的指令、编译器优化带来的提升大)。
    wefgonujnopu
        102
    wefgonujnopu  
    OP
       20 天前
    @deadpl 是没关系,python 只是为了对比,正常语言都是这样的,但是 js 的就和 await 有关系,所以 js 不正常
    wefgonujnopu
        103
    wefgonujnopu  
    OP
       20 天前
    @xz410236056 很多人看来无意义就是无意义吗,我觉得有意义,而且手写 promise 比这个难多了,面试也会问 js 的宏任务微任务执行过程,是很常见的问题
    lizhenda
        104
    lizhenda  
       20 天前
    笑死,这问题我在面试时还真问过,其实大多数中高级前端都基本能回答出来,一下没反应过来引导下也是可以,只是考查对 promise 实现的理解以及 js 事件循环有没有概念 await/async 本质是啥,编译后的产物有没有看过之类的。
    kongcc
        105
    kongcc  
       20 天前
    我不会写 但是我月薪 xxxx
    lizhenda
        106
    lizhenda  
       20 天前
    @lizhenda 还记得好多年前会问下在 es6 没出来前,用 js 自己怎么实现一个 class 的思路。最近这些年就只问 vue/react 之类的理解和实践了。
    wefgonujnopu
        107
    wefgonujnopu  
    OP
       20 天前
    @lizhenda 这么巧,还真有人问过,这个确实不难,只要理解透彻 promise 的都能回答出来,不过貌似 v2 很多人不会,一堆人嘲讽
    xz410236056
        108
    xz410236056  
       20 天前
    @wefgonujnopu #103
    1 、是的,当然你觉得好玩那就是有意义。
    2 、面试不是考试(国内喜欢把面试当考试纯属面试官菜),你筛选出知道这些人的目的是什么呢?
    wefgonujnopu
        109
    wefgonujnopu  
    OP
       20 天前
    @xz410236056 没什么目的啊,v2 不就是程序员交流的吗,而且也没筛选出来知道的人啊,就一个人回答正确了,其他的也就没回复了,正常交流技术而已
    mizuki9
        110
    mizuki9  
       20 天前
    确实没啥用,thenable 和 理解透彻 promise 有关系吗,知道 thenable 就透彻了?透彻在哪?
    作为考察前端知识的了解程度是可以的。
    wefgonujnopu
        111
    wefgonujnopu  
    OP
       20 天前
    @mizuki9 我说的是,理解透彻了,肯定知道 thenable 啊,你不知道 thenable 能算理解透彻? 但是知道 thenable 不代表理解透彻
    wangtian2020
        112
    wangtian2020  
       20 天前
    点开帖子,浏览问题,思考一会,发现不会
    勃然大怒,全文搜索,没人骂 op ,失望离开


    —— 像 promise.then 那样,await 允许我们使用 thenable 对象(那些具有可调用的 then 方法的对象)。这里的想法是,第三方对象可能不是一个 promise ,但却是 promise 兼容的:如果这些对象支持 .then ,那么就可以对它们使用 await
    https://zh.javascript.info/async-await
    johnnyyeen
        113
    johnnyyeen  
       20 天前
    大哥,递归一般是禁止在生产环境使用的,我记得我刚去腾讯那会,解决了一个前任的诡异 bug ,就是因为递归调用导致栈溢出。
    这是最基本的常识.......
    wefgonujnopu
        114
    wefgonujnopu  
    OP
       20 天前
    @johnnyyeen 说的是对 promise 的理解,递归是次要的,而且不同公司规定不一样,怎么可能全部禁用递归,递归也确实有优点,比如可读性高,代码简洁
    johnnyyeen
        115
    johnnyyeen  
       20 天前
    @wefgonujnopu 除了教学基本没优点,就如此简单。
    johnnyyeen
        116
    johnnyyeen  
       20 天前
    生产环境用递归的,见一个砍一个。
    wefgonujnopu
        117
    wefgonujnopu  
    OP
       20 天前
    @johnnyyeen 就是教学用的,跟手写 promise 一样,上面已经说了
    bowencool
        118
    bowencool  
       20 天前
    @Zhuzhuchenyan #1 涨姿势了
    enpitsulin
        119
    enpitsulin  
       20 天前
    就算用 Promise/PormiseLike/Thenable 就本质一个 callback 的 语法糖,做不到不阻塞线程也配叫 sleep ?没睡醒?

    [Atomics.wait]( https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Atomics#%E7%AD%89%E5%BE%85%E5%92%8C%E9%80%9A%E7%9F%A5_2)
    wefgonujnopu
        120
    wefgonujnopu  
    OP
       20 天前
    @enpitsulin 你说的啥,promise 本身就是不阻塞线程的
    AoEiuV020JP
        121
    AoEiuV020JP  
       20 天前
    不用看都知道帖子里的回复是“对人不对事”,明显主楼高高在上的态度惹人不快,回帖是找茬的没人想和你讨论什么,
    Mandelo
        122
    Mandelo  
       20 天前
    @wefgonujnopu #2 2025 了善用 AI ,
    Mandelo
        123
    Mandelo  
       20 天前
    @wefgonujnopu #2
    function sleep(ms) {
    return {
    then: function (resolve) {
    setTimeout(resolve, ms);
    }
    };
    }
    ✅ 用法:
    async function run() {
    console.log('start');
    await sleep(1000); // ✅ 正常延迟 1 秒
    console.log('after 1 second');
    }
    run();
    虽然没有写 new Promise(...),但依然是一个“类 Promise 对象”,这是唯一能让 await 起作用的方法。
    hzzhzzdogee
        124
    hzzhzzdogee  
       20 天前
    @Torpedo true
    wefgonujnopu
        125
    wefgonujnopu  
    OP
       20 天前
    @AoEiuV020JP 这样帖子才能火,这叫逆向思维,不然就是无人问津了
    unused
        126
    unused  
       20 天前 via Android
    await 会为 Thenable 创建 Promise
    ykrank
        127
    ykrank  
       20 天前
    @wefgonujnopu #47 imgur 都打不开的开发者...符合会问这种问题的刻板印象
    taotaodaddy
        128
    taotaodaddy  
       20 天前
    讨论的还挺热烈,不管怎么样,gemini 4o qwen 都可以,grok 不行,老马得加把劲了
    wefgonujnopu
        129
    wefgonujnopu  
    OP
       20 天前
    @ykrank 懂了."data":{"error":"Imgur is temporarily over capacity. Please try again later."},"success":false,"status":403} 所以 imgur 过载是我的问题
    Plumbiu
        130
    Plumbiu  
       20 天前
    跟楼主说一下,楼主的例子其实现实中也有类似的,例如轮询,对象的毕设用的 lvgl ,里面的回调事件就是 30ms 轮询监听的,另外我之前也写过监听窗口句柄变化的时尚小垃圾,也是过 1s 左右轮询一次。楼上觉得没实际意义大概率是见识太少了
    jiangzm
        131
    jiangzm  
       20 天前
    这个考查不了什么, 知道不加分不知道不减分
    jiangzm
        132
    jiangzm  
       20 天前
    楼主刚知道的一个基础知识点,准备用来做面试题, 笑死人,哈哈哈哈😂😂😂
    siweipancc
        133
    siweipancc  
       20 天前 via iPhone
    震惊我后端仔了
    maolon
        134
    maolon  
       20 天前
    hmmm 这种茴香豆的茴几种写法的东西除了考八股真有人会写到业务代码里去?
    flyn
        135
    flyn  
       20 天前 via Android
    虽然好多人在嘲讽楼主,但我还真是的涨见识了,因为我是业余的,和上面某个楼的前端新手的知识储备类似
    mightybruce
        136
    mightybruce  
       20 天前
    js 尾调用优化了解了解, 把递归变成循环。
    await 就是按照顺序执行,它就是阻塞, 我都没看到有人提到

    python 中是 async.gather

    js 中是 promise.all 来做

    如果不用 promise 还得研究一下。
    wefgonujnopu
        137
    wefgonujnopu  
    OP
       20 天前
    @mightybruce 我服了,js 没有尾调用优化好吗,你自己试试,不在末尾的 await,也不会触发堆栈溢出的
    wefgonujnopu
        138
    wefgonujnopu  
    OP
       20 天前
    @maolon 本来就是考八股的,什么时候说写业务了
    accelerator1
        139
    accelerator1  
       20 天前
    LZ 既然摆出了考验别人的姿态,那就要做好被别人质疑的准备,事情都有好坏方面。

    题目要求是 await sleep ,大部分想到的都是 thenable 对象了,楼上已经有人写出来了。

    原因 LZ 自己也说了,python 那个不行就是因为它的 await 不像 js 会清除调用栈变为事件循环。

    其实用阻塞 sleep 也行,只要有 async/await 关键字,就能清除调用栈保证不会栈溢出。

    ```
    function sleep(ms) {
    const start = Date.now();
    while (Date.now() - start < ms) {
    // 阻塞
    }
    }

    async function main(num=0){
    console.log(num)
    //去掉 await 会堆栈溢出
    await sleep(1);
    await main(num+1)
    }

    main()
    ```
    wefgonujnopu
        140
    wefgonujnopu  
    OP
       20 天前
    @accelerator1 是,但是题目已经说了,不能用阻塞 sleep
    accelerator1
        141
    accelerator1  
       20 天前
    @accelerator1 #138 我觉得这类问题挺好的,那些有异议的人应该先写出来、解释清楚再去反驳,"Talk is cheap, show me the code"。
    accelerator1
        142
    accelerator1  
       20 天前
    @wefgonujnopu #139 我也只是说明原理,并不是回答问题,在 js 单线程实现下,不阻塞的 sleep 只能宏任务 api ,相对不是那么准而已;想要准确的 sleep ,只能阻塞方式。题目说是实现 sleep ,其实更关注的其实是避免栈溢出。
    zangbianxuegu
        143
    zangbianxuegu  
       19 天前
    主题目 thenable 考察和附言的阻塞、堆栈溢出没什么关系。
    为什么 js 的 sleep 不会导致堆栈溢出,这其实是和**事件循环**相关,**事件循环是浏览器的机制**。
    可以看我这篇文章,以及里面大佬的视频演讲: https://mp.weixin.qq.com/s/MzAAa4ohk-75BHqGRH1XWQ

    简单说因为 sleep 实现用的是 setTimeout ,比如:

    ```js
    function loop() {
    setTimeout(loop, 0);
    }
    loop();
    ```
    这段代码永远不会导致浏览器卡死,setTimeout 创建任务加入任务队列,setTimeout 执行完会让出主线程控制权,进行渲染和其他任务。
    zangbianxuegu
        144
    zangbianxuegu  
       19 天前
    既说了不能阻塞线程,又觉得它不会造成堆栈溢出是不对的。这不是矛盾吗?
    wefgonujnopu
        145
    wefgonujnopu  
    OP
       19 天前
    @zangbianxuegu 你错了,和 setTimeout 没关系,你看这段代码有 setTimeout 么,为什么也不会溢出
    ···
    async function foo() {
    return 1
    }


    async function main(num = 0) {
    //去掉 foo 会堆栈溢出
    await foo()
    await main()

    }

    main()


    ···
    zangbianxuegu
        146
    zangbianxuegu  
       19 天前
    @wefgonujnopu #145
    不错,我说矛盾的说法也不准确。微任务确实不阻塞代码执行,也不会造成调用栈溢出。

    有 `await foo()` 不会导致堆栈溢出是因为,await 创建微任务,main() 当前的调用栈被清空,每一次都会被清空不会累加,所以不会出现:Maximum call stack size exceeded

    以上是在 Node 环境中。

    在浏览器环境中,有没有 `await foo()` 都会导致浏览器的卡死,因为阻塞渲染。
    chanderbing
        147
    chanderbing  
       19 天前
    感觉自己实现过 promise 的都能一秒想到吧,因为实现的时候就要对 thenable 对象进行处理。https://github.com/childrentime/wheel/blob/main/src/promise/index.js#L139-L158
    LawlietZ
        148
    LawlietZ  
       18 天前
    @lscho 刚学 js 的最喜欢鼓捣这些了,因为面试,然后有经验的人来回答,闭环了,有经验的人做面试官有时候也出这种面试题
    1  2  
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5683 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 02:53 · PVG 10:53 · LAX 19:53 · JFK 22:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.