代码如下:
import React from 'react';
import { useState, useEffect } from 'react';
export default function App() {
const [arr, setArr] = useState([0]);
useEffect(() => {
console.log(arr);
}, [arr]);
const handleClick = () => {
Promise.resolve()
.then(() => {
setArr(prevState => [...prevState, 1]);
})
.then(() => {
Promise.resolve()
.then(() => {
setArr(prevState => [...prevState, 2]);
})
.then(() => {
setArr(prevState => [...prevState, 5]);
})
.then(() => {
setArr(prevState => [...prevState, 6]);
});
})
.then(() => {
setArr(prevState => {
setArr(prevState => [...prevState, 4]);
return [...prevState, 3];
});
})
.catch(e => {
console.log(e);
});
};
return (
<>
<button onClick={handleClick}>change</button>
</>
);
}
点击按钮后,控制台的结果显示为
(1) [0]
(2) [0, 1]
(5) [0, 1, 2, 3, 4]
(6) [0, 1, 2, 3, 4, 5]
(7) [0, 1, 2, 3, 4, 5, 6]
想知道为啥结果不是[0, 1, 2, 5, 6, 3, 4]
,以及如何在 Promise 链中正确调用 setState,感谢大家的指点
1
jaylee4869 2023-12-10 13:42:35 +08:00
setState is an async function.
|
2
okakuyang 2023-12-10 13:50:29 +08:00
你第二个 then 开始就有问题了
|
3
whoami9426 OP @jaylee4869 这个我知道,但是为啥和 then 结合在一起感觉没有达到编码的预期效果
|
4
whoami9426 OP @okakuyang 是 Promise 嵌套的问题吗?
|
5
whoami9426 OP 附上代码的在线执行地址吧: https://playcode.io/1690189
|
6
common0 2023-12-10 14:32:50 +08:00 1
第二个 then() 里的 Promise.resolve() 前加上 return ,5 、6 就在 3 、4 前面了。
|
7
wipbssl 2023-12-10 14:37:58 +08:00 2
异步逻辑的问题,第二个 then 相当于 async 函数里放了一个 aync 函数,但没有加入 await ,所以执行到 setArr 2 时就异步,主线程继续执行第三个 then 了,5 和 6 要等到 2 的 promise resolve 后才会执行,但任务序列中第 3 个 then 在 setArr 2 resolve 前就加入队列了
|
8
chenliangngng 2023-12-10 15:22:06 +08:00 1
Promise 立即执行,当前轮宏任务
then 当前轮微任务 useState 浏览器空闲,也就是上一轮执行完了,下一轮宏任务 then 下一轮的微任务 |
9
onec 2023-12-10 15:36:27 +08:00
第一个 Promise.resolve()后立即执行 then1 回调,then1 返回 pending promise ,then2, then3 依次进入队列
then2 回调开始执行,then2.1 立即执行返回 pending promise, then2.2, then2.3, then2.3 进入队列 then3 执行 then2.2 执行 then2.3 执行 done |
10
SilencerL 2023-12-10 17:55:37 +08:00 1
简化一下你的代码:
Promise.resolve() .then(() => { setTimeout(() => console.info(1)) }) .then(() => { Promise.resolve() .then(() => { setTimeout(() => console.info(2)) }) .then(() => { setTimeout(() => console.info(5)) }) .then(() => { setTimeout(() => console.info(6)) }); }) .then(() => { setTimeout(() => { console.info(3) Promise.resolve().then(() => console.info(4)) }) }) 进一步简化: Promise.resolve() .then(() => { console.info(1) }) .then(() => { Promise.resolve() .then(() => { console.info(2) }) .then(() => { console.info(5) }) .then(() => { console.info(6) }); }) .then(() => { console.info(3) // console.info(4) // 1 2 3 4 5 6 // Promise.resolve().then(() => console.info(4)) // 1 2 3 5 4 6 // setTimeout(()=>console.info(4)) // 1 2 3 5 6 4 }) 你这个问题可以简化成和 React 没任何关系的问题,纯粹是浏览器任务队列的问题,Promise.resolve().then 可以生成一个微任务,setTimeout 或者你问题中的 setArr 生成的时宏任务(现代浏览器没得宏任务了,分成了更多任务列表,但是为了方便解释就还是说宏任务) 你可以观察进一步简化后的版本,以及看一下最后一个 then 里面关于不同方式 4 的输出时机,尝试理解一下。 但是不得不说,理解起来可能很困难,你需要了解 js 的事件循环以及队列优先级的问题。 大概来说,微任务优先级高,宏任务优先级低,每次事件循环按照优先级拿一遍任务。 - 最顶层的 Promise.resolve().then -> console.info(1) 立刻输出 1 - 第二个 then -- 第二个 then 里面 Promise.resolve().then -> console.info(2) 立刻输出 2 -- 第二个 then 里面 Promise.resolve() 的第一个 then 作为一个微任务已经结束,后续的第二个 then 扔到下一次微任务队列中 - 第三个 then -- 第一句 console.info(3) 立刻输出 3 -- 第二句: --- 情况 1:console.info(4) 那就立刻输出 4 --- 情况 2:Promise.resolve().then(() => console.info(4)) 扔一个微任务到下次事件队列,任务是 console.info(4) --- 情况 3:setTimeout(()=>console.info(4)) 扔一个宏任务到下次事件队列,任务是 console.info(4) - 下一次循环 -- 微任务队列有一个 .then(() => { console.info(5) }) .then(() => { console.info(6) }); --- 这里你可以看成一个新的 Promise.resolve() .then(()=> console.info(5)) .then(() => { console.info(6) }); (当然这不能真的这么看,但是为了讲解方便,你就这么理解好了。。。) --- 所以立即执行了这个微任务 console.info(5),把 console.info(6) 继续扔到微任务队列 -- 如果上一次循环的第三个 then 里面情况 2 ,那么在上一步的 5 输出结束后 6 的前面就有一个上一次扔过来的的微任务:console.info(4) -- 如果上一次是情况 3 ,输出 4 这个任务在宏任务队列,那么就先不管他,把当前下一个微任务输出 6 执行,再去执行宏任务队列 console.info(4) 单纯文字讲的讲不清楚,你要实际用代码多试几次,尽可能简化代码并且尝试不同的 case ,才能大概理解这个幺蛾子事件队列 顺便感谢你提供一道好玩的面试题,下次可以拿去为难其他人( |
11
otakustay 2023-12-10 21:48:26 +08:00
你这代码就是改成 console.log 也是 0, 1, 2, 3, 4, 5, 6 吧,和 state 一点关系都没有,纯 Promise 执行顺序问题
|
12
8XIQz5SCHX1U6c7s 2023-12-11 10:09:35 +08:00
好好好,又复习了一遍事件循环
|
13
lilei2023 2023-12-11 10:12:38 +08:00
这和 setState 没关系吧,这不就是 promise 执行问题么
|
15
chanChristin 2023-12-11 16:15:38 +08:00
有意思,所有的 ai 都认为答案是 [0, 1, 2, 5, 6, 3, 4]
|
16
CrispyNoodles 2023-12-11 18:00:24 +08:00
好好好,这样玩是吧。本来写完代码只是想摸下鱼,强制让我复习了一遍事件队列,内存,栈,堆的概念
|