DEV Community

Discussion on: An elegant solution for memory leaks in React

Collapse
 
mohantejach profile image
mohantejach • Edited

I have a solution, but the promise chaining is not possible in this case. I used an EventEmitter (observer pattern) to attach and detach callbacks.

function asyncCaller(asyncFun, params, thenBlock, catchBlock) {
  const id = getRandomString(10);
  EventEmitterInstance.addListener(`promiseThen_${id}`, thenBlock);
  EventEmitterInstance.addListener(`promiseCatch_${id}`, catchBlock);
  const cancel = () => {
    EventEmitterInstance.removeAllListeners(`promiseThen_${id}`);
    EventEmitterInstance.removeAllListeners(`promiseCatch_${id}`);
  };
  asyncFun(...params)
    .then((resp) => {
      EventEmitterInstance.emit(`promiseThen_${id}`, resp);
    })
    .catch((error) => {
      EventEmitterInstance.emit(`promiseCatch_${id}`, error);
    }).finally(() => {
      // need to clear the listeners at the end
      cancel();
    });
  return cancel;
}

// in the component hook

React.useEffect(() => {
    const thenBlock = () => {
      setText("done!");
    };
    const catchBlock = (error) => {
      console.log(error);
    };
    const cancelCall = asyncCaller(
      simulateSlowNetworkRequest,
      [],
      thenBlock,
      catchBlock
    );
    return () => {
      cancelCall();
    };
  }, [setText]);

Enter fullscreen mode Exit fullscreen mode

here is the working code link. I reused your example

Collapse
 
mohantejach profile image
mohantejach • Edited

an improved version of the above code : link

const PromiseObserver = new EventEmitter();

class AsyncAbort {
  constructor() {
    this.id = `async_${getRandomString(10)}`;
    this.asyncFun = null;
    this.asyncFunParams = [];
    this.thenBlock = null;
    this.catchBlock = null;
    this.finallyBlock = null;
  }

  addCall(asyncFun, params) {
    this.asyncFun = asyncFun;
    this.asyncFunParams = params;
    return this;
  }

  addThen(callback) {
    this.thenBlock = callback;
    return this;
  }

  addCatch(callback) {
    this.catchBlock = callback;
    return this;
  }

  addFinally(callback) {
    this.finallyBlock = callback;
    return this;
  }

  call() {
    const callback = ({ type, value }) => {
      switch (type) {
        case "then":
          if (this.thenBlock) this.thenBlock(value);
          break;
        case "catch":
          if (this.catchBlock) this.catchBlock(value);
          break;
        case "finally":
          if (this.finallyBlock) this.finallyBlock(value);
          break;
        default:
      }
    };
    PromiseObserver.addListener(this.id, callback);
    const cancel = () => {
      PromiseObserver.removeAllListeners(this.id);
    };
    this.asyncFun(...this.asyncFunParams)
      .then((resp) => {
        PromiseObserver.emit(this.id, { type: "then", value: resp });
      })
      .catch((error) => {
        PromiseObserver.emit(this.id, { type: "catch", value: error });
      })
      .finally(() => {
        PromiseObserver.emit(this.id, { type: "finally" });
        PromiseObserver.removeAllListeners(this.id);
      });
    return cancel;
  }
}

Enter fullscreen mode Exit fullscreen mode

in the useEffect hook you can do

React.useEffect(() => {
    const abort = new AsyncAbort()
      .addCall(simulateSlowNetworkRequest, [])
      .addThen((resp) => {
        setText("done!");
      })
      .addCatch((error) => {
        console.log(error);
      })
      .call();
    return () => {
      abort();
    };
  }, [setText]);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sotobry profile image
Bryann Sotomayor-Rinaldi

Wow, you're brilliant!