2014-03-28

simplify asynchronous method declarations with Task.async()

In Mozilla code, Task.spawn() is becoming a common way to implement asynchronous operations, especially methods like the greet method in this greeter object:

let greeter = {
  message: "Hello, NAME!",
  greet: function(name) {
    return Task.spawn((function*() {
      return yield sendGreeting(this.message.replace(/NAME/, name));
    }).bind(this);
  })
};

Task.spawn() makes the operation logic simple, but the wrapper function and bind() call required to start the task on method invocation and bind its this reference make the overall implementation complex.

Enter Task.async().

Like Task.spawn(), it creates a task, but it doesn't immediately start it. Instead, it returns an "async function" whose invocation starts the task, and the async function binds the task to its own this reference at invocation time. That makes it simpler to declare the method:

let greeter = {
  message: "Hello, NAME!",
  greet: Task.async(function*(name) {
    return yield sendGreeting(this.message.replace(/NAME/, name));
  })
};

With identical semantics:

greeter.greet("Mitchell").then((reply) => { ... }); // behaves the same

(And it avoids a couple anti-patterns in the process.)

Task.async() is inspired by ECMAScript's Async Functions strawman proposal and C#'s Async modifier and was implemented in bug 966182. It isn't limited to use in method declarations, although it's particularly helpful for them.

Use it to implement your next asynchronous operation!

2 comments:

Felipe G said...

Myk, that is really useful. Do you think it's valuable to have something to cover the following pattern as well?:

var obj = {

_funcPromise: null,
_myFunc: function() {
if (!this.funcPromise) {
this.funcPromise = Task.spawn(...);
}
return this.funcPromise;

}


in other words, a task that shouldn't have more than one instance of itself running concurrently.

Myk Melez said...

Hey Felipe, sorry that it's taken me so long to respond! Your code sample looks a lot like the asyncOnce helper function in toolkit/devtools/async-utils.js.

But the word "concurrently" throws me for a loop: do you mean that the task, once resolved, should be restarted the next time its async function is invoked? That would be something different, although also useful!

Either way, it all depends on how often you find yourself in the situation of needing to use that pattern, and how risky it is. If you're using it a lot, or it's risky (easy to get wrong, hard to diagnose bugs, etc.) then some sugar is very useful. But if you only use it occasionally, then it probably isn't worth it!