THIAGO DE BONIS
THIAGO DE BONIS

Reputation: 870

Event Listener running only once with Promise

I created a Promise here to connect to the storage and I also need to listen for changes.

With window.onstorage inside the promise it only listens once, even though it dispatches two events.

I can only listen to the two events dispatched if I put it directly in useEffect and without Promise.

Is it possible to listen to an event inside a Promise? If so how should I do it and what is wrong?

CODE

    const connect = useCallback(
        () =>
            new Promise<IConnectFunction>((resolve, reject) => {
                Promise.all([infuraStorageData, metamaskStorageData])
                    .then(response => {
                        window.onstorage = e => {
                            resolve({
                                infuraData: response[0],
                                metamaskData: response[1],
                            });
                        };
                    })
                    .catch((error: DOMException) => reject(error));
            }),
        [infuraStorageData, metamaskStorageData]
    );

useEffect(() => {
        useStorageDBHook
            .connect()
            .then(e => {
                console.log(e);
                // window.onstorage = e => {
                //  console.log(e);
                // };
            })
            .catch(e => {
                console.log(e);
            });
    }, [useStorageDBHook]);

Upvotes: 0

Views: 123

Answers (1)

hackape
hackape

Reputation: 19987

A Promise by design can only resolve once. Further call to resolve(value) has no effect.

If you want to receive (get notified) multiple events, you need some kind of stream interface (aka an observable, or subscribable), which will continuously feed you new data whenever available (in your case, whenever onstorage event fires).

A hand-rolled subscribable interface can be implemented like this:

class Subscribable {
  constructor(source) {
    this.subscribers = [];
    source(this.notify.bind(this));
  }

  subscribe(sink) {
    this.subscribers = this.subscribers.concat(push);
    const unsubscribe = () => {
      this.subscribers = this.subscribers.filter((_sink) => _sink !== sink);
    };
    return unsubscribe;
  }

  notify(newValue, error) {
    this.subscribers.forEach((sink) => sink(newValue, error));
  }
}

The usage is very close to Promise, you replace (resolve, reject) with a single notify callback, you cannot chain it with a .catch though, which is admittedly a bit drawback.

const connect = useCallback(
  () =>
    new Subscribable((notify) => {
      // I took the liberty to change your code a bit
      // cus it looks confusing, I guess your real intention is like:
      window.onstorage = (e) => { notify(e) };
      Promise.all([infuraStorageData, metamaskStorageData])
        .then((response) => {
          notify({
            infuraData: response[0],
            metamaskData: response[1],
          });
        })
        .catch((error) => notify(null, error));
    }),
  [infuraStorageData, metamaskStorageData]
);

useEffect(() => {
  const unsubscribe = connect().subscribe((newValue, error) => {
    if (error) {
      // handle error
    } else {
      console.log(newValue);
    }
  });
  return unsubscribe;
}, [connect]);

Upvotes: 1

Related Questions