Previous: Refactoring With Tasks
Next: Decorators
When a task is performed, it runs the code in the task function
you passed into task()
. This function must
be a generator function
— it must use the function *
syntax, and cannot
be just a regular JavaScript function.
This example demonstrates how, in ember-concurrency, generator functions behave just like regular functions. Anything you can do in a regular function, you can do in a generator function.
@task *pickRandomNumbers() { let nums = []; for (let i = 0; i < 3; i++) { nums.push(Math.floor(Math.random() * 10)); } this.set('status', `My favorite numbers: ${nums.join(', ')}`); }
yield
keyword
Much of the power of Tasks is unleashed once you start making
use of the yield
keyword within generator functions.
The yield
keyword, when used with a promise, lets you
pause execution of your task function until that promise resolves, at
which point the task function will continue running from where it
had paused.
This example demonstrates how you can yield timeout(1000)
to pause execution for 1000 ms (one second). The timeout()
helper function, which ember-concurrency provides,
simply returns a promise that resolves after the specified number of milliseconds.
@task *waitAFewSeconds() { this.set('status', 'Gimme one second...'); yield timeout(1000); this.set('status', 'Gimme one more second...'); yield timeout(1000); this.set('status', "OK, I'm done."); }
When you yield
a promise, the yield
expression
evaluates to the resolved value of the promise. In other words, you can
set a variable equal to a yielded promise, and when the promise resolves,
the task function will resume and the value stored into that variable will
be the resolved value of the promise.
@task *myTask() { this.set('status', `Thinking...`); let promise = timeout(1000).then(() => 123); let resolvedValue = yield promise; this.set('status', `The value is ${resolvedValue}`); }
If you yield
a promise that rejects, the task function will
throw the rejected value (likely an exception object) from the point in
task function where the rejecting promise was yielded. This means you can
use try {} catch(e) {} finally {}
blocks, just as you would
for code that runs synchronously.
@task *myTask() { this.set('status', `Thinking...`); try { yield timeout(1000).then(() => { throw 'Ahhhhh!!!!'; }); this.set('status', `This does not get used!`); } catch (e) { this.set('status', `Caught value: ${e}`); } }
The behavior of yielding promises within task generator functions
is designed to closely follow the behavior of
async/await
syntax, but instead of async function
, you use
function *
, and instead of await
, you
use yield
.
The decorator syntax and use with native classes is only fully supported when
using Ember 3.10+, but task()
can still be used with the Classic Ember
object model too. The Classic Ember usage remains much the same as the decorator
usage, but the syntax is slightly different.
Modifiers are called as functions off the task()
. e.g. calling
restartable()
on the task is the same as using
@task({ restartable: true })
when using decorators.
Note as well, the use of computed
use for the
favoriteNumbers
property. This is required for updates to be
reflected in the UI, if you were to use it within the template.
import Component from '@ember/component'; import { computed } from '@ember/object'; import { task } from 'ember-concurrency'; export default Component.extend({ tagName: '', status: null, favoriteNumbers: computed('pickRandomNumbers.last.value', function () { return this.pickRandomNumbers?.last?.value || []; }), pickRandomNumbers: task(function () { let nums = []; for (let i = 0; i < 3; i++) { nums.push(Math.floor(Math.random() * 10)); } this.set('status', `My favorite numbers: ${nums.join(', ')}`); return nums; }).restartable(), });
Support for the Classic Ember object model will be retained throughout ember-concurrency 2.x, but will be dropped in favor of supporting only native classes in the future.
Previous: Refactoring With Tasks
Next: Decorators