c-promise2- a promise library with cancellation, decorators, progress capturing, and more
Tags: javascript, react, promise, node
Published: 2020-12-08 19:25:55

Hi there! Just want some community feedback on my [c-promise2](https://www.npmjs.com/package/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](https://codesandbox.io/s/react-fetch-classes-decorators-tiny-forked-34vf2?file=/src/TestComponent.js)

```jsx
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");
  }
}
```

[Demo2](https://codesandbox.io/s/react-fetch-classes-decorators-forked-oyjf7?file=/src/TestComponent.js)

```jsx
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");
  }
}
```