DEV Community

Dmitriy Mozgovoy
Dmitriy Mozgovoy

Posted on

c-promise2- a promise library with cancellation, decorators, progress capturing, and more

Hi there! Just want some community feedback on my c-promise2 package that provides an extended promise class on top of the native. The library allows you to:

  • cancel a promise chain (with rejection)
  • use class decorators
  • pause/resume your promises
  • handle timeouts
  • capture promise chain progress
  • use internal/external AbortController for sending abort signals
  • use data flow

Any thoughts, which may arise when you see a code like the following are welcome :)
It's a generic React component that uses CPromise decorators to manage the life cycle of the async code including nested JSON requests. For example, this easily lets you avoid warnings like Warning: Can’t perform a React state update on an unmounted component when you unmount a component with running async tasks.
Demo1

import { async, listen, cancel, timeout } from "c-promise2";
import cpFetch from "cp-fetch";

export class TestComponent extends React.Component {
  state = {
    text: ""
  };

  @timeout(5000)
  @listen
  @async
  *componentDidMount() {
    console.log("mounted");
    const response = yield cpFetch(this.props.url);
    this.setState({ text: `json: ${yield response.text()}` });
  }

  render() {
    return <div>{this.state.text}</div>;
  }

  @cancel()
  componentWillUnmount() {
    console.log("unmounted");
  }
}
Enter fullscreen mode Exit fullscreen mode

Demo2

export class TestComponent extends React.Component {
  state = {
    text: "",
    items: [],
    progress: 0
  };

  constructor(props) {
    super(props);
    this.onAbortClick = this.onAbortClick.bind(this);
  }

  @cancel(E_REASON_CANCELED)
  onAbortClick() {}

  @canceled(function (err) {
    console.warn(`Canceled: ${err}`);
    if (err.code !== E_REASON_DISPOSED) {
      this.setState({ text: err + "" });
    }
  })
  @progress(function (progress) {
    console.log(`Progress: ${progress}`);
    this.setState({ progress });
  })
  @innerWeight(3)
  @listen
  @async
  *componentDidMount() {
    console.log("mounted");
    // concurrent requests with CPromise.all:
    const items = yield [
      this.fetchJSON(
        "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s"
      ),
      this.fetchJSON2(
        "https://run.mocky.io/v3/00758c80-8001-4bcc-bf7d-082ac3e1092b?mocky-delay=4s"
      )
    ];
    yield CPromise.delay(500); // just simulate another aync task
    yield CPromise.delay(500);
    yield CPromise.delay(500).weight(0);
    this.setState({ text: `${items.length} requests completed`, items });
  }

  @timeout(15000)
  @async
  *fetchJSON(url) {
    const response = yield cpFetch(url); // cancellable request
    return yield response.json();
  }

  @timeout(15000)
  @async
  *fetchJSON2(url) {
    const response = yield cpAxios(url);
    return response.data;
  }

  render() {
    return (
      <div>
        {this.state.text || (
          <div>
            <ProgressBar animated now={this.state.progress * 100 + ""} />
            <button
              type="button"
              className="btn btn-warning"
              onClick={this.onAbortClick}
            >
              Abort sequence
            </button>
          </div>
        )}
        <ul>
          {this.state.items.map((item, i) => (
            <li key={i}>{JSON.stringify(item)}</li>
          ))}
        </ul>
      </div>
    );
  }

  @cancel(E_REASON_DISPOSED)
  componentWillUnmount() {
    console.log("unmounted");
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)