Concept - Distilling how a promise works?
Can someone implement the most basic promise in a few lines?
Here it is:
function Promise(exec) {
// takes a function as an argument that gets the fullfiller
var callbacks = [], result;
exec(function fulfill() {
if (result) return;
result = arguments;
for (let c;c=callbacks.shift();)
c.apply(null, arguments);
});
this.addCallback = function(c) {
if (result)
c.apply(null, result)
else
callbacks.push(c);
}
}
Additional then
with chaining (which you will need for the answer):
Promise.prototype.then = function(fn) {
return new Promise(fulfill => {
this.addCallback((...args) => {
const result = fn(...args);
if (result instanceof Promise)
result.addCallback(fulfill);
else
fulfill(result);
});
});
};
How are these two snippets related?
ajax
is called from the getPromiseForAjaxResult
function:
function getPromiseForAjaxResult(ressource) {
return new Promise(function(callback) {
ajax({url:ressource}, callback);
});
}
Fundamentally, a promise is just an object that has a flag saying whether it's been settled, and a list of functions it maintains to notify if/when it is settled. Code can sometimes say more than words, so here's a very basic, not-real-world example purely indended to help communicate the concepts:
// See notes following the code for why this isn't real-world code
function Promise() {
this.settled = false;
this.settledValue = null;
this.callbacks = [];
}
Promise.prototype.then = function(f) {
if (this.settled) {
f(this.settledValue); // See notes 1 and 2
} else {
this.callbacks.push(f);
}
// See note 3 about `then`
// needing a return value
};
Promise.prototype.settle = function(value) { // See notes 4 and 5
var callback;
if (!this.settled) {
this.settled = true;
this.settledValue = value;
while (this.callbacks.length) {
callback = this.callbacks.pop();
callback(this.settledValue); // See notes 1 and 2
}
}
};
So the Promise
holds the state, and the functions to call when the promise is settled. The act of settling the promise is usually external to the Promise
object itself (although of course, that depends on the actual use, you might combine them — for instance, as with jQuery's ajax
[jqXHR
] objects).
Again, the above is purely conceptual and missing several important things that must be present in any real-world promises implementation for it to be useful:
then
andsettle
should always call the callback asynchronously, even if the promise is already settled.then
should because otherwise the caller has no idea whether the callback will be async.settle
should because the callbacks shouldn't run until aftersettle
has returned. (ES2015's promises do both of these things. jQuery'sDeferred
doesn't.)then
andsettle
should ensure that failure in the callback (e.g., an exception) is not propagated directly to the code callingthen
orsettle
. This is partially related to #1 above, and more so to #3 below.then
should return a new promise based on the result of calling the callback (then, or later). This is fairly fundamental to composing promise-ified operations, but would have complicated the above markedly. Any reasonable promises implementation does.We need different types of "settle" operation: "resolve" (the underlying action succeeded) and "reject" (it failed). Some use cases might have more states, but resolved and rejected are the basic two. (ES2015's promises have resolve and reject.)
We might make
settle
(or the separateresolve
andreject
) private in some way, so that only the creator of the promise can settle it. (ES2015 promises — and several others — do this by having thePromise
constructor accept a callback that receivesresolve
andreject
as parameter values, so only code in that callback can resolve or reject [unless code in the callback makes them public in some way].)
Etc., etc.