effection icon indicating copy to clipboard operation
effection copied to clipboard

Allow yielding a chain of `then` on `Task`

Open SebastienGllmt opened this issue 10 months ago • 2 comments
trafficstars

Motivation

There are cases where you need to do the following steps:

  1. Create a Task (using run)
  2. Do some computation on the Task using then
  3. yield the result of the task

example that shows how this can be used to:

  • implement #946 without requiring to use async/await explicitly anywhere
  • easily chain things without having to awkwardly wrap results with call as in #944
class Foo {
    task: Future<number>;
    
    constructor() {
       this.task = run(() => ...).then(val => val+1);
    }

    *doWork() {
        yield* this.task;
    }
}

This was a bit tedious to do previously (see #944), and this PR simplifies this case a bit by making that calling then on a Task returns a Future (which you can yield) instead of just a Promise

Approach & Alternate Designs

You can find some background in #944 and inline comments in this PR

TODOs and Open Questions

  • [ ] Do we want to make creating Futures more easier in general? This has a chance of overlapping conceptually with the pipe concept that was recently removed, and also overlaps with the idea of call (although call returns an Operation instead of a Future)
  • [ ] Is there a way to make that then returns a Task instead of a Future? I tried this initially, but ran into some complications: https://github.com/thefrontside/effection/issues/944#issuecomment-2564596508

SebastienGllmt avatar Dec 29 '24 12:12 SebastienGllmt

I'm wondering about the corner cases here.

  1. Will the returned promises be lazy? I.e.
  2. What if the return value is an operation vs a promise vs a regular value?

I.e. what would be the expectation around this:

run(fn).then(function*(value) { return value + 1 })

cowboyd avatar Dec 31 '24 20:12 cowboyd

  1. The laziness is not changed by this PR. Nothing happens (lazy) until you use await just like before this PR
  2. Currently, the behavior is the same as promises in generally. That is to say,
    1. if you return a value, it stays a value
    2. if you return a promise, it gets flattened to a value (.then(() => Promise.resolve(3)) becomes just 3)
    3. If you return a generator, it doesn't get flattened (it gets treated as case 1 and just returns a generator)

You could say that may we would like then to flatten generators as well for usability reasons, but this is not possible because the type definition of then comes from Promise (we cannot just arbitrarily change it). You could introduce a new function in the Future interface that flattens both promises in generators if we want

SebastienGllmt avatar Jan 01 '25 05:01 SebastienGllmt