question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RFC Proposal: Expose low-level coroutine functions

See original GitHub issue

Expose low-level coroutine functions

In the same vein of tj/co it would be helpful to use coroutine patterns in places outside of tasks.

Much of ember uses promises. Knowing that promises improve the state of asynchronous code, they also introduce a level of cognitive complexity that is difficult to maintain in many situations. e-c tasks and coroutines are interchangeable with promises.

This is a proposal to offer the same interchange functionality that e-c uses under the hood to manage tasks to the user so they can use them in places where a task would be inappropriate.

Possible use cases (non-exhaustive list)

  • Asynchronous test cases
  • beforeModel, model, and afterModel hooks
  • Custom networking services
  • Asynchronous procedures in build scripts
  • ember generate server code
  • Addon code (scripts used as part of blueprints and/or build hooks)

At the moment if you want to use coroutines in these cases you have to npm install co --save-dev which I think is node specific and not included in the final ember output.

Using other coroutine libraries is duplicating efforts since e-c already implements these under the hood.

Proposed API (A)

import { spawn } from 'ember-concurrency';

let promise1 = spawn(function * () {});  // => Promise
let wrapped = spawn.wrap(function * () {}); // => Function
let promise2 = wrapped();                // => Promise

Proposed API (B)

import { coroutine } from 'ember-concurrency';

let promise1 = coroutine(function * () {});    // => Promise
let wrapped = coroutine.wrap(function * () {}); // => Function
let promise2 = wrapped();                      // => Promise

Examples

test('…', spawn.wrap(function * (assert) {
  yield visit('…');
  yield clickOn('button');
  assert.ok($('button').is(':disabled'));
});
model: spawn.wrap(function * () {
  let data = yield $.ajax({url: '…'});
  let data2 = yield $.ajax({url: '…', data});
  return data2.map(itemData => MyItem.create(itemData));
})
model() {
  let users = spawn(function * () {
    let config = yield $.ajax({url: '/config'});
    return Ember.get(config, 'memberships.users');
  });

  let posts = $.ajax({url: '/posts'});

  return RSVP.hash({users, posts});
}
var spawn = require('ember-concurrency/spawn');

function wrap(gen) {
  let fn = spawn.wrap(gen);
  return function (req, res, next) {
    return fn(req, res, next).catch(next);
  };
}

module.exports = function (app) {
  app.get('/api/post', wrap(function * (req, res) {
    let data = {
      meta: { foo: 'bar' },
      data: yield getDataAsync()
    };
    res.setHeader('Content-Type', 'application.json');
    res.send(JSON.stringify(data));
  }));
};

Issue Analytics

  • State:open
  • Created 7 years ago
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
machtycommented, Nov 16, 2016

Anyone interested in implementation progress on this RFC can follow along w the to-function branch: https://github.com/machty/ember-concurrency/tree/to-function

2reactions
machtycommented, Oct 12, 2016

We’ve been discussing this RFC in the e-concurrency slack channel, and it seems like consensus is building for the following:

.toFunction()

Instead of having a separate .wrap function / chained method, we could just supply a .toFunction() task modifier. Unlike the other task modifiers (e.g. .drop() / .restartable()), .toFunction() wouldn’t just modify the TaskProperty descriptor but would actually produce an immediately-usable Function. This function could be callable on its own, or as a method of an object, e.g.

import { task, timeout } from 'ember-concurrency';

let fn = task(function * () {
  yield timeout(5000);
  // ...
  return 123;
}).toFunction();

fn().then((v) => {
  alert(v);
});

export default Ember.Component.extend({
  doStuff: fn,
});

The functions produced by .toFunction() share the same semantics as normal tasks, with the exception that there’s no concept of “top-level” Task state, like .isRunning or .isIdle. The reason for this is that

  1. this single task function will live on the prototype and be shared by all instance of the host class, and 2) even if you produced a new function for each instance of the host class, a) writing properties to instances of Function is slow in most JS engines, b) Ember core itself is stepping away from this pattern, and perhaps most importantly c) ember-metal explicitly forbids binding to properties on Function.

Supporting this will involve some internal reorganization; right now, task state lives on the Task object the each instance of a class that declares task, e.g. if FooComponent declares myTask: task(...), the state that tracks whether myTask is running lives inside an instance of Task on an instance of FooComponent. In order to support task functions preserving the same concurrency semantics as plain ol task()s, we’ll move this state to some shared, global cache keyed on (taskFunction, hostObject). I imagine there is a clever way to build this cache that leverages WeakMap if it exists, otherwise the cache can garbage collect hostObjects for which isDestroyed is true.

Concurrency semantics for task functions

let debouncedFn = task(function * () {
  yield timeout(500);
  console.log("Woot");
}).restartable().toFunction();

let obj0 = { debouncedFn };
let obj1 = { debouncedFn };

object0.debouncedFn(); // cancels immediately
object0.debouncedFn(); // cancels immediately
object0.debouncedFn(); // runs to completion

object1.debouncedFn(); // cancels immediately
object1.debouncedFn(); // cancels immediately
object1.debouncedFn(); // runs to completion

debouncedFn(); // cancels immediately
debouncedFn(); // cancels immediately
debouncedFn(); // runs to completion

The behavior of the above example is the same whether we’re using POJOs or Ember.Objects. The interesting bit here is that we’re using .restartable() here, which conceptually only makes sense when a task has an “owner”. With task functions, the “owner” is whatever the this context is when you invoke the task function. This why in the above example, calling object1.debouncedFn() doesn’t cancel the task instance returned from object0.debouncedFn(), but multiple calls to object0.debouncedFn() will cancel previous iterations. Functions called with a falsy context will share the same global context (chances are, in most cases, you’ll apply task modifiers to task functions that live on objects as methods).

Read more comments on GitHub >

github_iconTop Results From Across the Web

[Post-RFC] Stackless Coroutines - language design
I am creating this thread for continuing the discussion of the Stackless Coroutines RFC.
Read more >
[RFC] Parallel Abstraction For Tensors and Buffers
At a high-level, the proposed ops specify that a region that is evaluated multiple times in parallel, once per value of the single...
Read more >
I don't have time to write a whole essay, so let me just establish my ...
I wrote the generic associated types RFC (how Rust will implement higher ... the way the type system exposes low level details by...
Read more >
3128-io-safety - The Rust RFC Book
Earlier versions of this RFC proposed an IoSafe trait, which was meant as a minimally intrusive fix. Feedback from the RFC process led...
Read more >
[llvm-dev] [RFC] Adding CPS call support - Google Groups
> You can't branch across functions like you're proposing because the stack and callee-save registers won't be in the right state. LLVM will...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found