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

2024-04-07 10:06:25 +08:00
 wyc9296

我理解的是:下面的代码会先打印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)
3653 次点击
所在节点    Node.js
27 条回复
lisongeee
2024-04-07 10:12:12 +08:00
因为 Promise 的构造函数传入的函数参数是立刻执行
PaiDaTun
2024-04-07 10:15:16 +08:00
```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
2024-04-07 10:15:42 +08:00
因为你的 Promise 函数里并没有微任务或类似 setTimeout 的事件循环任务,你把计算步骤放到 setTimeout(func, 0) 或者 queueMicrotask 里再试试。
wyc9296
2024-04-07 10:19:52 +08:00
@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
2024-04-07 10:19:59 +08:00
JavaScript 在浏览器上,不使用 WebWorker 的情况下,并没有真正意义的并行执行,只不过是一堆后续操作和一堆任务及其逻辑依赖顺序罢了。
wyc9296
2024-04-07 10:23:15 +08:00
问了一下微软的 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
2024-04-07 10:26:03 +08:00
@wyc9296 #4

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

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

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

你别老整一堆回调试图用代码上的混乱打乱思维,你直接用 async await 线性流程写法就行
wyc9296
2024-04-07 10:27:26 +08:00
@thinkershare 我是直接在主机运行的 nodejs 呀、没有在浏览器上。我理解的是,它是单线程的,所以所有任务都需要排队,我对它的排队顺序有点懵。
wyc9296
2024-04-07 10:30:26 +08:00
@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
2024-04-07 10:30:33 +08:00
或者你直接看相关源码实现呗,你的计算操作在 https://github.com/zloirock/core-js/blob/66e55a9183394b88b5a71c2ded58719211f4421a/packages/core-js/modules/es.promise.constructor.js#L205 可以看到被直接执行
thinkershare
2024-04-07 10:30:45 +08:00
@wyc9296 nodejs 的默认行为和浏览器上没有区别。你都理解什么是后续任务了,就应该明白,调度器只保证 A->B 这种 B 总是在 A 完成 resolve 后执行,其它都是不保证的。
clue
2024-04-07 10:30:59 +08:00
认为 Promise 就是异步/微任务的, 都是理解不到家的

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

so ,js 中并不存在语言或者 js 引擎层面的任务排队这个概念,他就是一路同步执行下去的。然后 Peomis 构造函数是立刻执行,所以你在构造函数里做大量计算,这种会一直占用线程的操作的话,后面的操作自然不会去执行了
kenvix
2024-04-07 11:52:09 +08:00
Promise 并不会切换线程或者把任务丢到一个工作线程池去执行,而是就在当前线程池运行。而你的任务,恰恰就是计算密集型任务,线程资源全在跑你的循环上。跑完循环才能继续流程
kenvix
2024-04-07 11:53:00 +08:00
@kenvix #17 *口误,不是当前线程池,是当前线程
wpzz
2024-04-07 14:23:01 +08:00
🎯 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
2024-04-07 14:26:36 +08:00
@wpzz 我这还有用 TypeScript 手写 Promise 的 demo

https://juejin.cn/post/7098763156209532959

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/1030139

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX