V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wyc9296
V2EX  ›  Node.js

Promise 和异步回调和我想的不一样

  •  
  •   wyc9296 · 237 天前 · 3274 次点击
    这是一个创建于 237 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我理解的是:下面的代码会先打印123456,然后执行myfunc1函数的计算步骤,最后等待执行完毕后打印my func1。理论上主线程会先将myfunc1加入到 Promise 队列中,然后直接执行console.log(123456),最后再逐步清空任务队列、即执行myfunc1函数内 Promise 中的计算步骤。

    而实际上是:先执行myfunc1函数的计算步骤,执行完毕后打印123456,最后再打印my func1

    哪里没理解对呢?

    function myfunc1(){
        return new Promise((resolve,rejects)=>{
            /*这一段是计算步骤开始*/
            let i =1
            for (let index = 1; index < 100000000; index++) {
                i = 1
                for (let index_j = 1; index_j < 100; index_j++) {
                    i *=index_j
                }
            }
            /*这一段是计算步骤结束*/
            resolve("my func1")
        })
    }
    
    myfunc1()
    .then(result=>{
        console.log(result)
    })
    console.log(123456)
    
    26 条回复    2024-04-26 11:43:16 +08:00
    lisongeee
        1
    lisongeee  
       237 天前
    因为 Promise 的构造函数传入的函数参数是立刻执行
    PaiDaTun
        2
    PaiDaTun  
       237 天前
    ```javascript
    async function myfunc1() {
    return new Promise((resolve, rejects) => {
    /*这一段是计算步骤开始*/
    let i = 1
    for (let index = 1; index < 100000000; index++) {
    i = 1
    for (let index_j = 1; index_j < 100; index_j++) {
    i *= index_j
    }
    }
    /*这一段是计算步骤结束*/
    resolve("my func1")
    })
    }
    const r = await myfunc1()
    console.log(r)
    console.log(123456)
    ```
    onlxx123
        3
    onlxx123  
       237 天前
    因为你的 Promise 函数里并没有微任务或类似 setTimeout 的事件循环任务,你把计算步骤放到 setTimeout(func, 0) 或者 queueMicrotask 里再试试。
    wyc9296
        4
    wyc9296  
    OP
       237 天前
    @lisongeee Promise 不是异步调用的解决方案么?你的意思是说它是同步执行的?所以可以说下面的代码,
    myfunc1 和 myfunc2 是等价的吗?

    ```javascript
    function myfunc1(){
    return new Promise((resolve,rejects)=>{
    /*这一段是计算步骤开始*/
    let i =1
    for (let index = 1; index < 100000000; index++) {
    i = 1
    for (let index_j = 1; index_j < 100; index_j++) {
    i *=index_j
    }
    }
    /*这一段是计算步骤结束*/
    resolve("my func1")
    })
    }
    function myfunc2(){
    /*这一段是计算步骤开始*/
    let i =1
    for (let index = 1; index < 100000000; index++) {
    i = 1
    for (let index_j = 1; index_j < 100; index_j++) {
    i *=index_j
    }
    }
    /*这一段是计算步骤结束*/
    return new Promise((resolve,rejects)=>{
    resolve("my func2")
    })
    }
    ```
    thinkershare
        5
    thinkershare  
       237 天前   ❤️ 2
    JavaScript 在浏览器上,不使用 WebWorker 的情况下,并没有真正意义的并行执行,只不过是一堆后续操作和一堆任务及其逻辑依赖顺序罢了。
    wyc9296
        6
    wyc9296  
    OP
       237 天前
    问了一下微软的 Copilot ,感觉还是不够智能:

    @我:下面的代码会先打印 123456 ,然后执行 myfunc1 函数的计算步骤,最后等待执行完毕后打印 my func1 。实际是先执行 myfunc1 函数的计算步骤,执行完毕后打印 123456 ,再打印 my func1 。讲一下你的理解。

    @Copilot
    这段代码的执行顺序如下:
    首先,它会打印出 123456 。
    然后,它会开始执行 myfunc1 函数的计算步骤。这个函数使用了 Promise 来处理异步操作。在这个函数中,它进行了一个非常大的循环计算,计算了 100000000 次,每次都将 i 乘以 index_j 。
    当计算完成后,resolve("my func1") 会被调用,表示异步操作已经完成。
    最后,它会打印出 my func1 。
    实际上,由于 myfunc1 函数的计算步骤非常耗时,所以在执行完毕之前,它会先打印出 123456 ,然后再打印出 my func1 。
    这段代码的目的是演示异步操作和 Promise 的使用。它先执行了一个耗时的计算,然后在计算完成后才执行后续的操作。
    如果你还有其他问题,欢迎继续提问!
    lisongeee
        7
    lisongeee  
       237 天前
    @wyc9296 #4

    > Promise 不是异步调用的解决方案么?你的意思是说它是同步执行的?

    1. 是, 但不是最终,最终方案是 async/await + Promise
    2. 不完全是

    Promise 的构造函数的函数参数是立刻执行,执行完后的 Promise.then 一般是微任务执行,特殊情况 iOS 小程序是宏任务

    你别老整一堆回调试图用代码上的混乱打乱思维,你直接用 async await 线性流程写法就行
    wyc9296
        8
    wyc9296  
    OP
       237 天前
    @thinkershare 我是直接在主机运行的 nodejs 呀、没有在浏览器上。我理解的是,它是单线程的,所以所有任务都需要排队,我对它的排队顺序有点懵。
    wyc9296
        9
    wyc9296  
    OP
       237 天前
    @onlxx123 确实如你所说,谢谢你

    ```javascript
    function myfunc1_timeout(){
    return new Promise((resolve,rejects)=>{
    setTimeout(()=>{
    /*这一段是计算步骤开始*/
    let i =1
    for (let index = 1; index < 100000000; index++) {
    i = 1
    for (let index_j = 1; index_j < 100; index_j++) {
    i *=index_j
    }
    }
    /*这一段是计算步骤结束*/
    resolve("my func1")
    },0)

    })
    }

    myfunc1_timeout()
    .then(result=>{
    console.log(result)
    })
    console.log(123456)
    ```
    lisongeee
        10
    lisongeee  
       237 天前
    或者你直接看相关源码实现呗,你的计算操作在 https://github.com/zloirock/core-js/blob/66e55a9183394b88b5a71c2ded58719211f4421a/packages/core-js/modules/es.promise.constructor.js#L205 可以看到被直接执行
    thinkershare
        11
    thinkershare  
       237 天前
    @wyc9296 nodejs 的默认行为和浏览器上没有区别。你都理解什么是后续任务了,就应该明白,调度器只保证 A->B 这种 B 总是在 A 完成 resolve 后执行,其它都是不保证的。
    clue
        12
    clue  
       237 天前   ❤️ 4
    认为 Promise 就是异步/微任务的, 都是理解不到家的

    你这代码里, Promise 构造函数传入的初始化函数是同步执行的, 只有 then 传入的回调函数, 在 resolve("my func1") 执行完后被加到微任务队列中
    Track13
        13
    Track13  
       237 天前
    js 是单线程的。你这段代码没有需要等的地方。
    DICK23
        14
    DICK23  
       237 天前
    Promise 构造函数是同步执行,后往 micro task queue 里加 then 中的函数,接着继续执行剩余的同步代码,等执行栈空了,再从队列中取出任务进到执行栈中,输出 my func1
    onerd
        15
    onerd  
       237 天前
    八股经典的手写 promise 看一下 myfunc1()这里 executor 立刻执行了
    BeautifulSoap
        16
    BeautifulSoap  
       237 天前 via Android
    lz 对 js promises 和 js 的异步的理解有误。Promise 的异步本质是回调函数,以前 js 没有 Promise 的时候,js 和 Python 这些脚本一样,要实现异步只有用回调函数( callback function )来实现,结果就是导致回调地狱的出现。后来为了解决这个问题才有了 Promise ,但 Promise 本质上还是更好用的回调函数,并没有改变任何 js 的底层。然后 async await 是 Promise 的语法糖,async await 的代码看起来像是同步,实际上依旧是同步执行的回调

    so ,js 中并不存在语言或者 js 引擎层面的任务排队这个概念,他就是一路同步执行下去的。然后 Peomis 构造函数是立刻执行,所以你在构造函数里做大量计算,这种会一直占用线程的操作的话,后面的操作自然不会去执行了
    kenvix
        17
    kenvix  
       237 天前
    Promise 并不会切换线程或者把任务丢到一个工作线程池去执行,而是就在当前线程池运行。而你的任务,恰恰就是计算密集型任务,线程资源全在跑你的循环上。跑完循环才能继续流程
    kenvix
        18
    kenvix  
       237 天前
    @kenvix #17 *口误,不是当前线程池,是当前线程
    wpzz
        19
    wpzz  
       237 天前
    🎯 new Promise 本身是同步, resolve,reject 是异步,await promise 会 阻塞下面代码的执行。

    myfunc1()
    .then(result=>{
    console.log(result)
    })// 这里会解析这个函数,变成 100000000 同步计算,计算需要时间,会卡住下面的同步打印


    console.log(123456)

    // 如果调换一下顺序就可以看出,立马打印后,计算一会再打印出 resolve
    console.log(123456)
    myfunc1()
    .then(result=>{
    console.log(result)
    })


    主要还是「 new Promise 本身是同步」这个概念

    ```
    // 这一部分代码是同步的
    let i =1
    for (let index = 1; index < 100000000; index++) {
    i = 1
    for (let index_j = 1; index_j < 100; index_j++) {
    i *=index_j
    }
    }

    // resolve 的会进入到异步
    resolve("my func1")

    ```

    https://juejin.cn/post/7096407782118653965
    wpzz
        20
    wpzz  
       237 天前
    @wpzz 我这还有用 TypeScript 手写 Promise 的 demo

    https://juejin.cn/post/7098763156209532959
    realJamespond
        21
    realJamespond  
       237 天前
    promise 和 settimeout 一起用才是异步,不然就是同步,make sense ?
    msg7086
        22
    msg7086  
       237 天前
    我来盲猜一下。
    你以为 Promise 跑程序就像 Promise 跑 AJAX 一样是异步的,因为跑 AJAX 的时候,是先执行了后面的代码,再在 AJAX 收到回复以后异步执行 then()的内容。
    但这个的异步是来自 AJAX (Asynchronous JavaScript and XML)里的 Asynchronous 而不是来自 Promise 。不管你用的是不是 Promise ,用 AJAX 就是异步的,不使用 AJAX 的代码默认就是同步的。
    同理 setTimeout 也是一个异步功能,不管你用不用 Promise 他都是异步的。
    msg7086
        23
    msg7086  
       237 天前
    换一种说法,不管是 Promise 也好 await 也好,要实现异步执行,必须要用异步执行功能。不管是 AJAX 也好,还是 setTimeout 也好,用了这些,你的程序才能异步起来,然后 Promise 也好 await 也好,才能用上异步执行功能。如果你的代码本来就是同步的,那你写 Promise 或者 await 都是白写,因为没有异步代码可以给你 await 。
    JJvaryBig
        24
    JJvaryBig  
       237 天前
    解释执行顺序如下:

    调用 myfunc1():

    函数 myfunc1() 被调用,它立即返回一个 Promise 对象。这个 Promise 对象会立即开始执行其内部的构造函数提供的异步任务。
    执行 Promise 构造函数中的异步任务:

    异步任务(即传给 Promise 构造函数的回调函数)开始执行。这里,该任务包含两个嵌套的循环,进行大量的计算工作。
    计算过程中,控制台不会有任何输出,因为所有操作都在内存中进行,且由于计算量大,这部分可能耗时较长。
    当计算步骤全部完成(即所有循环迭代结束),resolve("my func1") 被调用,将字符串 "my func1" 作为成功的结果传递给 Promise 链。
    同步代码继续执行:

    在 myfunc1() 返回 Promise 并开始执行异步任务后,JavaScript 引擎继续执行后面的同步代码。
    下一行是 console.log(123456),因此立即输出数字 123456 到控制台。
    微任务检查点:

    当当前执行上下文(如事件循环的一个宏任务)中的所有同步代码执行完毕后,事件循环到达一个检查点,此时会处理待处理的微任务队列。
    由于 myfunc1() 返回的 Promise 在之前已执行 resolve(),对应的 .then() 方法被注册为一个微任务,等待执行。
    执行 .then() 中的回调:

    微任务队列中的第一个任务就是之前注册的 .then() 回调,该回调函数接收 result 参数(即 "my func1")并执行 console.log(result)。
    控制台输出字符串 "my func1",这是整个程序中的最后一个操作。
    综上所述,代码的执行顺序是:

    创建并开始执行 Promise ,其中包含大量计算。
    同步输出 123456 到控制台。
    计算完成后,Promise 解决,将 "my func1" 放入微任务队列。
    事件循环处理微任务,执行 .then() 回调,输出 "my func1" 到控制台。
    所以,最终在控制台看到的输出顺序是:
    // 123456
    // my func1
    vialon17
        25
    vialon17  
       236 天前
    copy 了代码直接扔到 firefox 里面跑了下,
    这个函数看来会直接阻塞主线程,并不是异步/微县城。
    forty
        26
    forty  
       218 天前
    等价于以下代码, 这样看是不是更好理解了?

    ```
    async function myfunc1(){
    /*这一段是计算步骤开始*/
    let i =1
    for (let index = 1; index < 100000000; index++) {
    i = 1
    for (let index_j = 1; index_j < 100; index_j++) {
    i *=index_j
    }
    }
    /*这一段是计算步骤结束*/
    return "my func1";
    }

    myfunc1() // 同步执行
    .then(result=>{ // 异步回调
    console.log(result)
    })

    console.log(123456)
    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   918 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 21:17 · PVG 05:17 · LAX 13:17 · JFK 16:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.