
代码如下:
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 lavvrence 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 第二个 then() 里的 Promise.resolve() 前加上 return ,5 、6 就在 3 、4 前面了。 |
7 wipbssl 2023-12-10 14:37:58 +08:00 异步逻辑的问题,第二个 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 Promise 立即执行,当前轮宏任务 then 当前轮微任务 useState 浏览器空闲,也就是上一轮执行完了,下一轮宏任务 then 下一轮的微任务 |
9 onec 2023-12-10 15:36:27 +08:00 第一个 Promise.resolve()后立即执行 then1 回调,then1 返回 pending promise ,then2, then3 依次进入队列br />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 简化一下你的代码: 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 Leeeeex PRO 有意思,所有的 ai 都认为答案是 [0, 1, 2, 5, 6, 3, 4] |
16 CrispyNoodles 2023-12-11 18:00:24 +08:00 好好好,这样玩是吧。本来写完代码只是想摸下鱼,强制让我复习了一遍事件队列,内存,栈,堆的概念 |