如何让 useEffect 支持 async/await?
创始人
2024-03-24 16:59:03

大家在使用 useEffect 的时候,假如回调函数中使用 async...await... 的时候,会报错如下。

看报错,我们知道 effect function 应该返回一个销毁函数(return返回的 cleanup 函数),如果 useEffect 第一个参数传入 async,返回值则变成了 Promise,会导致 react 在调用销毁函数的时候报错**。

React 为什么要这么做?


useEffect 作为 Hooks 中一个很重要的 Hooks,可以在函数组件中执行副作用操作。

它能够完成之前 Class Component 中的生命周期的职责。它返回的函数的执行时机如下:

  • 首次渲染不会进行清理,会在下一次渲染,清除上一次的副作用。
  • 卸载阶段也会执行清除操作。

不管是哪个,我们都不希望这个返回值是异步的,这样无法预知代码的执行情况,很容易出现难以定位的 Bug。

所以 React 就直接限制了不能 useEffect 回调函数中不能支持 async...await...

useEffect 怎么支持 async...await...


竟然 useEffect 的回调函数不能使用 async...await,那我直接在它内部使用。

做法一:创建一个异步函数(async...await 的方式),然后执行该函数。

useEffect(() => {const asyncFun = async () => {setPass(await mockCheck());};asyncFun();
}, []);

做法二:也可以使用 IIFE,如下所示:

useEffect(() => {(async () => {setPass(await mockCheck());})();
}, []);

自定义 hooks


既然知道了怎么解决,我们完全可以将其封装成一个 hook,让使用更加的优雅。我们来看下 ahooks 的 useAsyncEffect,它支持所有的异步写法,包括 generator function

思路跟上面一样,入参跟 useEffect 一样,一个回调函数(不过这个回调函数支持异步),另外一个依赖项 deps。内部还是 useEffect,将异步的逻辑放入到它的回调函数里面。

function useAsyncEffect(effect: () => AsyncGenerator | Promise,// 依赖项deps?: DependencyList,
) {// 判断是 AsyncGeneratorfunction isAsyncGenerator(val: AsyncGenerator | Promise,): val is AsyncGenerator {// Symbol.asyncIterator: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator// Symbol.asyncIterator 符号指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于for await...of循环。return isFunction(val[Symbol.asyncIterator]);}useEffect(() => {const e = effect();// 这个标识可以通过 yield 语句可以增加一些检查点// 如果发现当前 effect 已经被清理,会停止继续往下执行。let cancelled = false;// 执行函数async function execute() {// 如果是 Generator 异步函数,则通过 next() 的方式全部执行if (isAsyncGenerator(e)) {while (true) {const result = await e.next();// Generate function 全部执行完成// 或者当前的 effect 已经被清理if (result.done || cancelled) {break;}}} else {await e;}}execute();return () => {// 当前 effect 已经被清理cancelled = true;};}, deps);
}

async...await 我们之前已经提到了,重点看看实现中变量 cancelled 的实现的功能。 它的作用是中断执行

通过 yield 语句可以增加一些检查点,如果发现当前 effect 已经被清理,会停止继续往下执行。

试想一下,有一个场景,用户频繁的操作,可能现在这一轮操作 a 执行还没完成,就已经开始开始下一轮操作 b。这个时候,操作 a 的逻辑已经失去了作用了,那么我们就可以停止往后执行,直接进入下一轮操作 b 的逻辑执行。这个 cancelled 就是用来取消当前正在执行的一个标识符。

还可以支持 useEffect 的清除机制么?


可以看到上面的 useAsyncEffect,内部的 useEffect 返回函数只返回了如下:

return () => {// 当前 effect 已经被清理cancelled = true;
};

这说明,你通过 useAsyncEffect 没有 useEffect 返回函数中执行清除副作用的功能

你可能会觉得,我们将 effect(useAsyncEffect 的回调函数)的结果,放入到 useAsyncEffect 中不就可以了?

实现最终类似如下:

function useAsyncEffect(effect: () => Promise void)>, dependencies?: any[]) {return useEffect(() => {const cleanupPromise = effect()return () => { cleanupPromise.then(cleanup => cleanup && cleanup()) }}, dependencies)
}

这种做法在github上也有讨论,上面有个大神的说法:

他认为这种延迟清除机制是不对的,应该是一种取消机制。否则,在钩子已经被取消之后,回调函数仍然有机会对外部状态产生影响。跟 useAsyncEffect 其实思路是一样的,如下:

实现:

function useAsyncEffect(effect: (isCanceled: () => boolean) => Promise, dependencies?: any[]) {return useEffect(() => {let canceled = false;effect(() => canceled);return () => { canceled = true; }}, dependencies)
}

Demo:

useAsyncEffect(async (isCanceled) => {const result = await doSomeAsyncStuff(stuffId);if (!isCanceled()) {// TODO: Still OK to do some effect, useEffect hasn't been canceled yet.}
}, [stuffId]);

其实归根结底,我们的清除机制不应该依赖于异步函数,否则很容易出现难以定位的 bug

总结与思考

由于 useEffect 是在函数式组件中承担执行副作用操作的职责,它的返回值的执行操作应该是可以预期的,而不能是一个异步函数,所以不支持回调函数 async...await 的写法。

可以将 async...await 的逻辑封装在 useEffect 回调函数的内部,这就是 hooks useAsyncEffect 的实现思路,而且它的范围更加广,它支持的是所有的异步函数,包括 generator function

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...