GithubHelp home page GithubHelp logo

wagenet / ember-concurrency-ts Goto Github PK

View Code? Open in Web Editor NEW

This project forked from chancancode/ember-concurrency-ts

0.0 0.0 0.0 812 KB

TypeScript utilities for ember-concurrency.

Home Page: http://ember-concurrency.com/docs/typescript

License: MIT License

JavaScript 29.72% HTML 6.97% TypeScript 63.31%

ember-concurrency-ts's Introduction

ember-concurrency-ts

TypeScript utilities for ember-concurrency.

This is how you would typically write ember-concurrency tasks in Octane using ember-concurrency-decorators:

import { action } from '@ember/object';
import Component from '@glimmer/component';
import { TaskGenerator, timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';

export default class extends Component {
  @task *myTask(ms: number): TaskGenerator<string> {
    yield timeout(ms);
    return 'done!';
  }

  @action performTask() {
    if (this.myTask.isRunning) {
      return;
    }

    this.myTask.perform(1000).then(value => {
      console.log(value.toUpperCase());
    });
  }
}

Since we are using native classes in Octane, TypeScript have an easier time understanding and following our code. Normally, this is a good thing, but in the case of ember-concurrency, it ends up getting a bit in the way.

ember-concurrency's API was designed with Class Ember in mind, where it could decorate a property or method and replace it with a different type in the .extend() hook.

This is not allowed using TypeScript's decorators. Since myTask is defined using as the generator method syntax, and since methods do not have a .perform() method on them, calling this.myTask.perform() will result in a type error, even though it will work at runtime.

We could work around this by type casting the method, such as (this.myTask as any as Task<string, number>), but doing this everywhere is quite verbose and error-prone.

Instead, this addon provides some TypeScript-specific utility functions to encapsulate the type cast transparently. See the Usage section for details.

Compatibility

Installation

ember install ember-concurrency-ts

Optionally, if using ember-concurrency-async, add the following to types/<app name>/index.d.ts:

import 'ember-concurrency-async';
import 'ember-concurrency-ts/async';

Usage

taskFor

The taskFor utility function allows the code example from above to type check:

import { action } from '@ember/object';
import Component from '@glimmer/component';
import { TaskGenerator, timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { taskFor } from 'ember-concurrency-ts';

export default class extends Component {
  @task *myTask(ms: number): TaskGenerator<string> {
    yield timeout(ms);
    return 'done!';
  }

  @action performTask() {
    if (taskFor(this.myTask).isRunning) {
      return;
    }

    taskFor(this.myTask).perform(1000).then(value => {
      console.log(value.toUpperCase());
    });
  }
}

Instead of accessing the task directly, wrapping it in the taskFor utility function will allow TypeScript to understand what we are trying to accomplish. If this becomes repetitive, you may extract it into a variable or getter, and the code will still type check:

import { action } from '@ember/object';
import Component from '@glimmer/component';
import { TaskGenerator, timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { taskFor } from 'ember-concurrency-ts';

export default class extends Component {
  @task *myTask(ms: number): TaskGenerator<string> {
    yield timeout(ms);
    return 'done!';
  }

  @action performTask() {
    let myTask = taskFor(this.myTask);

    if (myTask.isRunning) {
      return;
    }

    myTask.perform(1000).then(value => {
      console.log(value.toUpperCase());
    });
  }
}

Note that everything on the task is type-inferred from the method definition. Based on the return type of *myTask, TypeScript knows that myTask.value is string | undefined. Likewise, it knows that myTask.perform() takes the same arguments as *myTask. Passing the wrong arguments will be a type error. It also knows that the value promise callback parameter is a string.

Alternate usage of taskFor

The taskFor utility function can also be used at assignment:

import { action } from '@ember/object';
import Component from '@glimmer/component';
import { TaskGenerator, timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { taskFor } from 'ember-concurrency-ts';

export default class extends Component {
  @task myTask = taskFor(function*(ms: number): TaskGenerator<string> {
    yield timeout(ms);
    return 'done!';
  });

  @action performTask() {
    if (this.myTask.isRunning) {
      return;
    }

    this.myTask.perform(1000).then(value => {
      console.log(value.toUpperCase());
    });
  }
}

This allows you to access the task directly without using taskFor and perform. The one caveat here is that the this type must be asserted if you are referencing this in your task:

import { action } from '@ember/object';
import Component from '@glimmer/component';
import { TaskGenerator, timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { taskFor } from 'ember-concurrency-ts';

export default class MyComponent extends Component {
  returnVal = 'done';

  @task myTask = taskFor(function*(this: MyComponent, ms: number): TaskGenerator<string> {
    yield timeout(ms);
    return this.returnVal;
  });

  @action performTask() {
    if (this.myTask.isRunning) {
      return;
    }

    this.myTask.perform(1000).then(value => {
      console.log(value.toUpperCase());
    });
  }
}

perform

As a convenience, this addon also provide a perform utility function as a shorthand for myTask(...).perform(...):

import { action } from '@ember/object';
import Component from '@glimmer/component';
import { TaskGenerator, timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { perform } from 'ember-concurrency-ts';

export default class extends Component {
  @task *myTask(ms: number): TaskGenerator<string> {
    yield timeout(ms);
    return 'done!';
  }

  @action performTask() {
    perform(this.myTask, 1000).then(value => {
      console.log(value.toUpperCase());
    });
  }
}

Just like taskFor, it infers the type information from *myTask, type checks the arguments as has the right return type, etc.

ember-concurrency-async

This addon can be used together with ember-concurrency-async, see the Installation section for additional instructions.

import { action } from '@ember/object';
import Component from '@glimmer/component';
import { timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { taskFor, perform } from 'ember-concurrency-ts';

export default class extends Component {
  @task async myTask(ms: number): Promise<string> {
    await timeout(ms);
    return 'done!';
  }

  @action performTask() {
    if (taskFor(this.myTask).isRunning) {
      return;
    }

    perform(this.myTask, 1000).then(value => {
      console.log(value.toUpperCase());
    });
  }
}

Type Safety

Under-the-hood, these utility functions are just implemented as unsafe type casts. For example, the examples will still type check if the @task decorator is omitted (so this.myTask is just a regular generator or async method), but you will get an error at runtime.

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.

ember-concurrency-ts's People

Contributors

chancancode avatar dependabot[bot] avatar ember-tomster avatar jamescdavis avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.