Automatically invalidate async listeners and promises in one place.
Lightweight zero dependent library for browsers.
Libraries like React, should invalidate all async tasks once component is unmounted,
using isMounted is anti pattern.
npm i --save browser-cancelable-eventsThe project is written with typescript and comes with a built-in index.d.ts
import { CancelableEvents, isCancelledPromiseError } from "browser-cancelable-events";const { CancelableEvents, isCancelledPromiseError } = require("browser-cancelable-events");Usage example using react component
import { Component } from 'react'
import { CancelableEvents, isCancelledPromiseError } from "browser-cancelable-events";
class MyComponent extends Component {
constructor(props) {
super(props);
this.cancelable = new CancelableEvents();
}
componentDidMount() {
this.cancelable.addWindowEventListener("resize", this.onWindowResize.bind(this));
this.cancelable.addDocumentEventListener("keydown", (e) => {
if (e.shiftKey) {
// shift clicked
}
});
this.updateEverySecond();
this.fetchDataFromApi();
}
// invalidate all events
componentWillUnmount() {
this.cancelable.cancelAll(); // this is the magic line
}
render() {
return (
<div> ... </div>
);
}
// interval that updates every second
updateEverySecond() {
this.cancelable.setInterval(() => {
this.setState({
counter: this.state.counter + 1,
})
}, 1000);
}
// do task with timeout
doSomeAnimation() {
this.setState({
isInAnimation: true,
}, () => {
this.cancelable.setTimeout(() => {
this.setState({
isInAnimation: false,
});
}, 500);
})
}
// use invalidated promise
async fetchDataFromApi() {
try {
const apiResult = await this.cancelable.promise(() => {
return APIService.fetchSomeData();
});
const someVeryLongPromise = this.cancelable.promise(() => {
return APIService.fetchSomeReallyLongTask();
});
this.cancelable.setTimeout(() => {
// 1 second is too much, let's cancel
someVeryLongPromise.cancel();
}, 1000);
await someVeryLongPromise;
} catch (err) {
if (isCancelledPromiseError(err)) {
// all good, component is not mounted or promise is cancelled
return;
}
// it's real error, should handle
}
}
// callback for window.addEventListener
onWindowResize(e) {
// do something with resize event
}
}Each one of the cancelable methods returns object with cancel method
{
cancel: Function
}const timer = cancelable.setInterval(intervalCallback, 100);
timer.cancel();Cancel all listeners method, invalidated everything immediately. Adding new listeners on cancelled event will throw exception
cancelable.cancelAll();cancelable.setTimeout(callback, time, ...args[]) -> CancelableObjectcancelable.setInterval(callback, time, ...args[]) -> CancelableObjectcancelable.promise(functionThatReturnsPromise, ...args[]) -> Promise & CancelableObject
cancelable.promise(promise) -> Promise<T> & CancelableObjectconst cancelable = new CancelableEvents();
cancelable.promise(new Promise((resolve, reject) => {
resolve("foo");
})).then((res) => {
console.log(res); // foo
});
let cancelTimer;
// invalidate promise after 1 second
const promise = cancelable.promise(() => API.fetchSomeLongData());
promise.then((data) => {
if(cancelTimer){
cancelTimer.cancel();
}
}).catch((err) => {
if(isCancelledPromiseError(err)){
// timeout, took too long
return;
};
// real error
});
// if promise.cancel() called after fulfilled, nothing will happen
cancelTimer = cancelable.setTimeout(() => {
promise.cancel();
}, 1000);Observes document.addEventListener
cancelable.addDocumentEventListener(eventKey, callback) -> CancelableObjectObserves window.addEventListener
cancelable.addWindowEventListener(eventKey, callback) -> CancelableObjectconst cancelable = new CancelableEvents();
cancelable.addDocumentEventListener("mousewheel", (e) => {
// do something with event
});
cancelable.addWindowEventListener("submit", (e) => {
// do something with event
});
// remove all cancelable listeners
cancelable.cancelAll();You can use custom event emitter with cancelable events.
cancelable.addCustomCancelable(subscription, removeKey) -> CancelableObjectconst { EventEmitter } = require("fbemitter");
const emitter = new EventEmitter();
// key "remove" because emitter.addListener(...).remove()
cancelable.addCustomCancelable(emitter.addListener("myCustomEvent", callback), "remove");npm run testsMIT