jayphelps / core-decorators

Library of stage-0 JavaScript decorators (aka ES2016/ES7 decorators but not accurate) inspired by languages that come with built-ins like @​override, @​deprecate, @​autobind, @​mixin and more. Popular with React/Angular, but is framework agnostic.
MIT License
4.52k stars 263 forks source link

Return promises where appropriate #64

Closed RomkeVdMeulen closed 8 years ago

RomkeVdMeulen commented 8 years ago

Some decorators can turn synchronous function asynchronous, such as @debounce and @throttle. It would be nice to be able to do something like:

class Editor {

  content = '';

  @debounce(500)
  updateContent(content) {
    this.content = content;
  }

  updateAndRender(content) {
    this.updateContent(content).then(() => Renderer.render(this));
  }
}
RomkeVdMeulen commented 8 years ago

Hmm, come to think about it, if updateAndRender is called twenty times, you would't want the callback to be executed twenty times once the debounced updateContent completes. Perhaps you could only fulfil the first promise returned and ignore the rest? But that might introduce race-conditions:

class Editor {

  content = '';

  @debounce(500)
  updateContent(content) {
    this.content = content;
  }

  updateAndRender(content) {
    this.updateContent(content).then(() => Renderer.render(this));
  }

  updateAndSomethingElse(content) {
    this.updateContent(content).then(() => somethingElse());
  }
}

Not sure what might be a good solution. I'll have to give it some more thought.

jayphelps commented 8 years ago

@RomkeVdMeulen hmm yeah, please think this one over some more and let me know your thoughts. My initial reaction is hesitation since promises are once-and-done but these functions you decorate are almost always not.

RomkeVdMeulen commented 8 years ago

I've ended up with doing the administration myself in my current project. Something like this (TypeScript):

class ItemService {

    private queue: { [id: number]: Deferred<Item> } = {};

    @debounce(500)
    retrieve() {
        const batch = this.queue;
        this.queue = {};
        const query = "?id=" + Object.keys(batch).join("&id=");
        this.backend.getStream(this.getResourceUrl() + query, (item: Item) => {
            batch[item.id].resolve(item);
        });
    }

    getItem(id: number): Promise<Item> {
        if (!this.queue[id]) {
            this.queue[id] = new Deferred();
        }
        this.retrieve();
        return this.queue[id].promise;
    }
}

This way each call to getItem() stil gets a relevant promise, and the actual retrieval can be batched.

Though this is, I think, a useful pattern, it depends on too much case-specific information to somehow incorporate it into a decorator. I can't figure out any general solution either, so I'll just close this.

jayphelps commented 8 years ago

@RomkeVdMeulen thanks for clarifying! Let me know if you think of a way to include it while making everyone happy 👍