# use-async-effect

# introduction

useEffect钩子🪝可以帮我们处理程序中的副作用,比如调用api、定时器等。

用法也很简单,useEffect(cb, [deps])接收两个参数,第一个是必填的回调函数,第二个是选填的依赖项。如果用依赖项,依赖项改变会让回调函数重新执行。

但有一点需要我们特别注意的是回调函数只能返回function,而使用async声明的函数会返回Promise对象,导致程序报错。

作为一个那么重要的钩子🪝居然不能用async,这就有点不爽了。下面来看一个实现可以用async作为回调的useEffect

# use-async-effect

use-async-effectnpm包帮我们加强了useEffect,我们可以在use-async-effect函数中使用async函数。用法如下:

useAsyncEffect(async () => {
  await doSomethingAsync();
});

# API

The API is the same as React's useEffect(), except for some notable differences:

  • The destroy function is passed as an optional second argument:
useAsyncEffect(callback, dependencies?);
useAsyncEffect(callback, onDestroy, dependencies?);
  • The async callback will receive a single function to check whether the component is still mounted:
useAsyncEffect(async isMounted => {
  const data1 = await fn1();
  if (!isMounted()) return;

  const data2 = await fn2();
  if (!isMounted()) return;

  doSomething(data1, data2);
});

# Examples

Basic mount/unmount

useAsyncEffect(async () => console.log('mount'), () => console.log('unmount'), []);

Omitting destroy

useAsyncEffect(async () => console.log('mount'), []);

Handle effect result in destroy

useAsyncEffect(() => {
  const timer = setInterval(() => console.log('timer run'), 3000);
  return () => clearInterval(timer);
}, (result) => {
  console.log(result);
  result();  // 清除定时器
})

Making sure it's still mounted before updating component state

useAsyncEffect(async isMounted => {
  const data = await fetch(`/users/${id}`).then(res => res.json());
  if (!isMounted()) return;
  setUser(data);
}, [id]);

# 源码分析

var React = require('react');
/**
 * 
 * @param effect {function} 回调函数,处理副作用
 * @param destroy {function | array} 类似cleanup function(回调函数返回的函数)或依赖项
 * @param inputs {array} 依赖性
 * 
 **/
function useAsyncEffect(effect, destroy, inputs) {
  // 判断第二个参数是否为函数
  var hasDestroy = typeof destroy === 'function';

  React.useEffect(function () {
    var result; // 记录 effect 函数的返回值
    var mounted = true;  // 标记📌调用组件的当前状态
    var maybePromise = effect(function () {
      return mounted;
    });

    // 将 effect 返回值包裹成 Promise 对象,然后通过 then 函数取出原始值,复制给 result 变量
    Promise.resolve(maybePromise).then(function (value) {
      result = value;
    });

    return function () {
      // clean function 执行后将 mounted 标记为 false
      mounted = false;

      // 如果有传 destroy 就执行
      if (hasDestroy) {
        destroy(result);
      }
    };
  }, hasDestroy ? inputs : destroy);
}

# 参考

use-async-effect (opens new window)
Homebrew React Hooks: useAsyncEffect Or How to Handle Async Operations with useEffect (opens new window)