Have a javascript function pass a reference to itself in to another function

76 Views Asked by At

I found myself continuously writing the same shape of code for asynchronous calls so I tried to wrap it up in something that would abstract some of the details. What I was hoping was that in my onError callback I could pass a reference of the async function being executed so that some middleware could implement retry logic if it was necessary. Maybe this is a code smell that I'm tackling this the wrong way but I'm curious if it's possible or if there are other suggestions for handling this.

const runAsync = (asyncFunc) => {
  let _onBegin = null;
  let _onCompleted = null;
  let _onError = null;
  let self = this;
  return {
    onBegin(f) {
      _onBegin = f;
      return this;
    },
    onCompleted(f) {
      _onCompleted = f;
      return this;
    },
    onError(f) {
      _onError = f;
      return this;
    },
    async execute() {
      if (_onBegin) {
        _onBegin();
      }
      try {
        let data = await asyncFunc();
        if (_onCompleted) {
          _onCompleted(data);
        }
      } catch (e) {
        if (_onError) {
          _onError(e ** /*i'd like to pass a function reference here as well*/ ** );
        }
        return Promise.resolve();
      }
    },
  };
};

await runAsync(someAsyncCall())
  .onBegin((d) => dispatch(something(d)))
  .onCompleted((d) => dispatch(something(d)))
  .onError((d, func) => dispatch(something(d, func)))
  .execute()
1

There are 1 best solutions below

0
Mulan On

I'm thinking you could use a custom hook. Something like -

import { useState, useEffect } from 'react'

const useAsync = (f) => {
  const [state, setState] =
    useState({ loading: true, result: null, error: null })

  const runAsync = async () => {
    try {
      setState({ ...state, loading: false, result: await f })
    }
    catch (err) {
      setState({ ...state, loading: false, error: err })
    }
  }

  useEffect(_ => { runAsync() }, [])

  return state
}

Now we can use it in a component -

const FriendList = ({ userId }) => {
  const response =
    useAsync(UserApi.fetchFriends(userId)) // <-- some promise-returning call

  if (response.loading)
    return <Loading />
  else if (response.error)
    return <Error ... />
  else
    return <ul>{response.result.map(Friend)}</ul>
}

The custom hook api is quite flexible. The above approach is naive, but we can think it through a bit more and make it more usable -

import { useState, useEffect } from 'react'

const identity = x => x

const useAsync = (runAsync = identity, deps = []) => {
  const [loading, setLoading] = useState(true)
  const [result, setResult] = useState(null)
  const [error, setError] = useState(null)

  useEffect(_ => { 
    Promise.resolve(runAsync(...deps))
      .then(setResult, setError)
      .finally(_ => setLoading(false))
  }, deps)

  return { loading, error, result }
}

Custom hooks are dope. We can make custom hooks using other custom hooks -

const fetchJson = (url = "") =>
  fetch(url).then(r => r.json())  // <-- stop repeating yourself

const useJson = (url = "") => // <-- another hook
  useAsync(fetchJson, [url]) // <-- useAsync

const FriendList = ({ userId }) => {
  const { loading, error, result } =
    useJson("some.server/friends.json") // <-- dead simple

  if (loading)
    return <Loading .../>

  if (error)
    return <Error .../>

  return <ul>{result.map(Friend)}</ul>
}