-2

I wish to return a Promise which is self-resolved at a later time, but it seems that my syntax is invalid, and I'm curious what a better implementation would be. Why is an executor required for the Promise constructor?

promise = new Promise() is invalid because I need to supply a function

function getDetails (someHash) {
    var url = "http://somewebsite.com/" + someHash,
        promise = new Promise();

     makeAjaxRequest(url, function (response) {
         promise.resolve(response.data);
     });

     setTimeout(function () {
         promise.reject("timeout");
     }, 500);

     return promise;
}

function makeAjaxRequest (url, callback) {
    var someResponse = {data: "some data"};

    setTimeout(function () {
        callback(someResponse);
    }, 200);
}

Is there a better way to implement this functionality?

neaumusic
  • 10,027
  • 9
  • 55
  • 83
  • What is `{}` at the end of the first `function` line? – Barmar Jun 06 '16 at 06:57
  • That looks like your syntax error. – Barmar Jun 06 '16 at 06:57
  • I'm not sure why this was downvoted but this is a pretty good question in my opinion. – Benjamin Gruenbaum Jun 06 '16 at 13:01
  • Why don't you just *use* an executor to make your code work? – Bergi Jun 06 '16 at 18:03
  • 1
    @BenjaminGruenbaum: The first question seems to be a dupe of http://stackoverflow.com/questions/37651780/why-does-the-promise-constructor-need-an-executor that you just answered, the second question seems to be some kind of R[TFM](http://stackoverflow.com/questions/22519784/how-do-i-convert-an-existing-callback-api-to-promises) – Bergi Jun 06 '16 at 18:05
  • Marking as a dup because it appears that you just want to gripe some more about why the Promise interface is the way it is and that has already been answered for you in your previous question. Please focus on the best way to solve your actual problem rather than griping about the way the promise interface is designed because we can only offer solutions to a problem - we can't change the promise design. Questions here should focus on a specific problem you want help with, not a gripe session that repeats questions already answered. – jfriend00 Jun 07 '16 at 07:41
  • Just for the record - the other question was asked after this one, I am frustrated by the design because all people say is "that's how it is" but JavaScript is supposed to be malleable – neaumusic Jun 07 '16 at 08:21
  • @jfriend00 Where should I go to promote discussion on Promise restrictions? I'm realizing that it's an incredible tool, and it should be resettable, it should allow triggers from outside of the constructor, and the sequential tree of callbacks should be edditable. It's essentially an async, one-shot AND gate.. – neaumusic Jun 07 '16 at 23:46
  • @neaumusic - Here [Why does the Promise constructor need an executor?](http://stackoverflow.com/questions/37651780/why-does-the-promise-constructor-need-an-executor/37673534#37673534) I gave you 8 lines of code that lets you resolve promises outside the constructor. If you want to discuss it with those involved in the Promise standards process, you will need to go find the relevant mailing list for that subject. That isn't here. Here, we discuss how it works now and how to solve problems with it (the way it is). Also, this isn't a "discussion" forum. – jfriend00 Jun 08 '16 at 12:10

2 Answers2

5

Note: If you want to convert a callback API to promises see this question.

Let's start with something that should be said from the get go. An executor is not required in order to design promises. It is entirely possible to design a promises implementation that does something like:

let {promise, resolve, reject} = Promise.get();

If you promise not to tell anyone, I'll even let you in on a little secret - this API even exists from a previous iteration of the spec and in fact even still works in Chrome:

let {promise, resolve, reject} = Promise.defer();

However, it is being removed.

So, why do I need to pass an executor?

I just answered your other question about why an executor is an interesting design. It's throw-safe and it lets you take care of interesting things.

Can I still get the resolve, reject functions? I need those

In my experience, most of the times I needed resolve/reject I didn't actually need them. That said - I did in fact actually need them a couple of times.

The specifiers recognized this and for this reason the executor function is always run synchronously. You can get the .defer interface it just isn't the default:

function defer() {
    let resolve, reject, promise = new Promise((res, rej) => {
      [resolve, reject] = [res, rej];
    });
    return {resolve, reject, promise};
}

Again, this is typically not something you want to do but it is entirely possible that you have a justifiable use case which is why it's not the default but completely possible.

Your actual code

I would start with implementing things like timeout and a request as primitives and then compose functions and chain promises:

function delay(ms) {
  return new Promise(r => setTimeout(r, ms));
}
function timeout(promise, ms) {
   return Promise.race([
      promise,
      delay(ms).then(x => { throw new Error("timeout"); })
   ]);
}
function ajax(url) { // note browsers do this natively with `fetch` today
   return new Promise((resolve, reject) => { // handle errors!
      makeAjaxRequest(url, (result) => {
          // if(result.isFailure) reject(...);
          if(result.status >= 400) reject(new Error(result.status));
          else resolve(result.data);
      });
   });
}

Now when we promisified the lowest level API surface we can write the above code quite declaratively:

function getDetails (someHash) {
    var ajax = makeAjaxRequest("http://somewebsite.com/" + someHash);
    return timeout(ajax, 500);
}
Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • I believe your `defer` function is a syntax error because the first two `const` variables are lacking initialisers. – Bergi Jun 06 '16 at 18:07
  • Notice that I've never found a case where a deferred-like pattern was necessary but I could not use the `Promise` constructor instead. And `deferred.reject` is quite unnecessary as you can always do `resolve(Promise.reject(…))`, which further simplifies most of the cases where you need to store your resolver somewhere. – Bergi Jun 06 '16 at 18:09
  • @Bergi basically, the pattern is useful when you want to hold a promise but not actually start the action yet changing the default semantics - for example consider an Image tag, I might want an `.loaded` promise that fulfills when the image is loaded but I might want to not actually load the image if it's not attached to the DOM. I need to provide the promise _before_ the image download has started. (I can of course solve this by having another `attached` promise and set loaded to a `then` of that, but how would I store `attached`?) – Benjamin Gruenbaum Jun 06 '16 at 21:24
  • I meant a pattern where I needed a `deferred` object that holds both the promise and a resolving capability. Sure, storing `resolve` elsewhere before calling it later is useful sometimes, but I always do that using the `Promise` constructor directly. Regarding your example, you actually can simply create the image object and install `resolve`/`reject` as `onload`/`onerror` handlers before starting to load anything by setting the `src` - then just do that when you're attaching the image. – Bergi Jun 06 '16 at 21:45
  • @Bergi I meant - if you were the implementor of image, not as a promisifer. – Benjamin Gruenbaum Jun 06 '16 at 23:35
3

You need to pass a function to the Promise constructor (more info), which will get called to provide the resolve and reject functions:

function getDetails (someHash) {
  var url = "http://somewebsite.com/" + someHash;

  return new Promise(function(resolve, reject) {
    makeAjaxRequest(url, function(err, response) {
      if (err)
        reject(err);
      else
        resolve(response.data);
    });

    setTimeout(function () {
      reject("timeout");
    }, 500);
  });
}

function makeAjaxRequest (url, callback) {
  var someResponse = {data: "some data"};

  setTimeout(function () {
    callback(null, someResponse);
  }, 200);
}

I've also taken the liberty to make makeAjaxRequest use the standard convention of passing errors as first argument (because I assume that at some point you want to replace the setTimeout() with an actual AJAX request).

robertklep
  • 198,204
  • 35
  • 394
  • 381