Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the Promise constructor need an executor?

When using Promises, why can't triggers for resolve and reject be defined elsewhere in the codebase?

I don't understand why resolve and reject logic should be localized where the promise is declared. Is this an oversight, or is there a benefit to mandating the executor parameter?


I believe the executor function should be optional, and that its existence should determine whether the promise encapsulates resolution or not. The promise would be much more extensible without such mandates, since you don't have to initiate async right away. The promise should also be resettable. It's a 1 shot switch, 1 or 0, resolve() or reject(). There are a multitude of parallel and sequential outcomes that can be attached: promise.then(parallel1) and promise.then(parallel2) and also promise.then(seq1).then(seq2) but reference-privileged players cannot resolve/reject INTO the switch

You can construct a tree of outcomes at a later time, but you can't alter them, nor can you alter the roots (input triggers)

Honestly, the tree of sequential outcomes should be edittable as well.. say you want to splice out one step and do something else instead, after you've declared many promise chains. It doesn't make sense to reconstruct the promise and every sequential function, especially since you can't even reject or destroy the promise either...

like image 700
neaumusic Avatar asked Jun 06 '16 07:06

neaumusic


People also ask

What is executor in Promise?

The executor is custom code that ties an outcome in a callback to a promise.

Why Promise executor functions should not be async?

The executor function can also be an async function . However, this is usually a mistake, for a few reasons: If an async executor function throws an error, the error will be lost and won't cause the newly-constructed Promise to reject. This could make it difficult to debug and handle some errors.

How do you make a Promise without executing?

Without Executing If you find yourself wanting a "cold" promise in the sense that your promise doesn't execute until you await on it, you should just use an async function. Calling an async function returns a new promise every time.

Can a constructor return a Promise?

Finally, as the constructor returns, that object becomes the result of the new WebPage("url") expression. Since the new -expression returns the newly created object, returning a Promise is not an option. Therefore it's not possible to perform an asynchronous call to its completion during this process.


2 Answers

This is called the revealing constructor pattern coined by Domenic.

Basically, the idea is to give you access to parts of an object while that object is not fully constructed yet. Quoting Domenic:

I call this the revealing constructor pattern because the Promise constructor is revealing its internal capabilities, but only to the code that constructs the promise in question. The ability to resolve or reject the promise is only revealed to the constructing code, and is crucially not revealed to anyone using the promise. So if we hand off p to another consumer, say

The past

Initially, promises worked with deferred objects, this is true in the Twisted promises JavaScript promises originated in. This is still true (but often deprecated) in older implementations like Angular's $q, Q, jQuery and old versions of bluebird.

The API went something like:

var d = Deferred();
d.resolve(); 
d.reject();
d.promise; // the actual promise

It worked, but it had a problem. Deferreds and the promise constructor are typically used for converting non-promise APIs to promises. There is a "famous" problem in JavaScript called Zalgo - basically, it means that an API must be synchronous or asynchronous but never both at once.

The thing is - with deferreds it's possible to do something like:

function request(param) {
   var d = Deferred();
   var options = JSON.parse(param);
   d.ajax(function(err, value) { 
      if(err) d.reject(err);
      else d.resolve(value);
   });
}

There is a hidden subtle bug here - if param is not a valid JSON this function throws synchronously, meaning that I have to wrap every promise returning function in both a } catch (e) { and a .catch(e => to catch all errors.

The promise constructor catches such exceptions and converts them to rejections which means you never have to worry about synchronous exceptions vs asynchronous ones with promises. (It guards you on the other side by always executing then callbacks "in the next tick").

In addition, it also required an extra type every developer has to learn about where the promise constructor does not which is pretty nice.

like image 57
Benjamin Gruenbaum Avatar answered Oct 16 '22 15:10

Benjamin Gruenbaum


FYI, if you're dying to use the deferred interface rather than the Promise executor interface despite all the good reasons against the deferred interface, you can code one trivially once and then use it everywhere (personally I think it's a bad idea to code this way, but your volume of questions on this topic suggests you think differently, so here it is):

function Deferred() {
    var self = this;
    var p = this.promise = new Promise(function(resolve, reject) {
        self.resolve = resolve;
        self.reject = reject;
    });
    this.then = p.then.bind(p);
    this.catch = p.catch.bind(p);
    if (p.finally) {
        this.finally = p.finally.bind(p);
    }
}

Now, you can use the interface you seem to be asking for:

var d = new Deferred();
d.resolve(); 
d.reject();
d.promise;     // the actual promise
d.then(...)    // can use .then() on either the Deferred or the Promise
d.promise.then(...)

Here a slightly more compact ES6 version:

function Deferred() {
    const p = this.promise = new Promise((resolve, reject) => {
        this.resolve = resolve;
        this.reject = reject;
    });
    this.then = p.then.bind(p);
    this.catch = p.catch.bind(p);
    if (p.finally) {
        this.finally = p.finally.bind(p);
    }
}

Or, you can do what you asked for in your question using this Deferred() constructor:

var request = new Deferred();
request.resolve();
request.then(handleSuccess, handleError);

But, it has the downsides pointed out by Benjamin and is not considered the best way to code promises.

Something similar shown here on MDN.

like image 38
jfriend00 Avatar answered Oct 16 '22 14:10

jfriend00