How Is a Promise/Defer Library Implemented

How is a promise/defer library implemented?

I find it harder to explain than to show an example, so here is a very simple implementation of what a defer/promise could be.

Disclaimer: This is not a functional implementation and some parts of the Promise/A specification are missing, This is just to explain the basis of the promises.

tl;dr: Go to the Create classes and example section to see full implementation.

Promise:

First we need to create a promise object with an array of callbacks. I'll start working with objects because it's clearer:

var promise = {
callbacks: []
}

now add callbacks with the method then:

var promise = {
callbacks: [],
then: function (callback) {
callbacks.push(callback);
}
}

And we need the error callbacks too:

var promise = {
okCallbacks: [],
koCallbacks: [],
then: function (okCallback, koCallback) {
okCallbacks.push(okCallback);
if (koCallback) {
koCallbacks.push(koCallback);
}
}
}

Defer:

Now create the defer object that will have a promise:

var defer = {
promise: promise
};

The defer needs to be resolved:

var defer = {
promise: promise,
resolve: function (data) {
this.promise.okCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(data)
}, 0);
});
},
};

And needs to reject:

var defer = {
promise: promise,
resolve: function (data) {
this.promise.okCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(data)
}, 0);
});
},

reject: function (error) {
this.promise.koCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(error)
}, 0);
});
}
};

Note that the callbacks are called in a timeout to allow the code be always asynchronous.

And that's what a basic defer/promise implementation needs.

Create classes and example:

Now lets convert both objects to classes, first the promise:

var Promise = function () {
this.okCallbacks = [];
this.koCallbacks = [];
};

Promise.prototype = {
okCallbacks: null,
koCallbacks: null,
then: function (okCallback, koCallback) {
okCallbacks.push(okCallback);
if (koCallback) {
koCallbacks.push(koCallback);
}
}
};

And now the defer:

var Defer = function () {
this.promise = new Promise();
};

Defer.prototype = {
promise: null,
resolve: function (data) {
this.promise.okCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(data)
}, 0);
});
},

reject: function (error) {
this.promise.koCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(error)
}, 0);
});
}
};

And here is an example of use:

function test() {
var defer = new Defer();
// an example of an async call
serverCall(function (request) {
if (request.status === 200) {
defer.resolve(request.responseText);
} else {
defer.reject(new Error("Status code was " + request.status));
}
});
return defer.promise;
}

test().then(function (text) {
alert(text);
}, function (error) {
alert(error.message);
});

As you can see the basic parts are simple and small. It will grow when you add other options, for example multiple promise resolution:

Defer.all(promiseA, promiseB, promiseC).then()

or promise chaining:

getUserById(id).then(getFilesByUser).then(deleteFile).then(promptResult);

To read more about the specifications: CommonJS Promise Specification. Note that main libraries (Q, when.js, rsvp.js, node-promise, ...) follow Promises/A specification.

Hope I was clear enough.

Edit:

As asked in the comments, I've added two things in this version:

  • The possibility to call then of a promise, no matter what status it has.
  • The possibility to chain promises.

To be able to call the promise when resolved you need to add the status to the promise, and when the then is called check that status. If the status is resolved or rejected just execute the callback with its data or error.

To be able to chain promises you need to generate a new defer for each call to then and, when the promise is resolved/rejected, resolve/reject the new promise with the result of the callback. So when the promise is done, if the callback returns a new promise it is bound to the promise returned with the then(). If not, the promise is resolved with the result of the callback.

Here is the promise:

var Promise = function () {
this.okCallbacks = [];
this.koCallbacks = [];
};

Promise.prototype = {
okCallbacks: null,
koCallbacks: null,
status: 'pending',
error: null,

then: function (okCallback, koCallback) {
var defer = new Defer();

// Add callbacks to the arrays with the defer binded to these callbacks
this.okCallbacks.push({
func: okCallback,
defer: defer
});

if (koCallback) {
this.koCallbacks.push({
func: koCallback,
defer: defer
});
}

// Check if the promise is not pending. If not call the callback
if (this.status === 'resolved') {
this.executeCallback({
func: okCallback,
defer: defer
}, this.data)
} else if(this.status === 'rejected') {
this.executeCallback({
func: koCallback,
defer: defer
}, this.error)
}

return defer.promise;
},

executeCallback: function (callbackData, result) {
window.setTimeout(function () {
var res = callbackData.func(result);
if (res instanceof Promise) {
callbackData.defer.bind(res);
} else {
callbackData.defer.resolve(res);
}
}, 0);
}
};

And the defer:

var Defer = function () {
this.promise = new Promise();
};

Defer.prototype = {
promise: null,
resolve: function (data) {
var promise = this.promise;
promise.data = data;
promise.status = 'resolved';
promise.okCallbacks.forEach(function(callbackData) {
promise.executeCallback(callbackData, data);
});
},

reject: function (error) {
var promise = this.promise;
promise.error = error;
promise.status = 'rejected';
promise.koCallbacks.forEach(function(callbackData) {
promise.executeCallback(callbackData, error);
});
},

// Make this promise behave like another promise:
// When the other promise is resolved/rejected this is also resolved/rejected
// with the same data
bind: function (promise) {
var that = this;
promise.then(function (res) {
that.resolve(res);
}, function (err) {
that.reject(err);
})
}
};

As you can see, it has grown quite a bit.

Deferred/promise confusion and implementation

If I would like to implement deferred by myself, would this be a right way to do?

No, your code misses a few important points:

  • A promise/deferred represents one single outcome only. It's state must not be changed after it is settled (fulfilled or rejected).
  • No matter when a callback is installed, it will be executed as soon as possible with the result. Your code fails to do that when the result has already arrived.
  • No matter how many callbacks are installed, they all will be executed. Your code allows to store only a single callback.

You might want to have a look at this example implementation or How is a promise/defer library implemented?.

Also, for a promise you will want to implement an interoperable then method for chaining, whose behaviour is described in the Promises/A+ specification.

Does deferred counts as a behavioural pattern?

Yeah, you could count them as a mix of observer or visitor pattern.

What is the difference between deferred and promise?

In general, a deferred does provide the methods to fulfill or reject, while a promise is the interface to install the callbacks on (without the ability to resolve the promise). See What are the differences between Deferred, Promise and Future in JavaScript? for details.

JavaScript Promise/Defer in Chrome

You are creating unnecessary function objects.

You can just do:

var defer = function() {
var result = {};
result.promise = new Promise(function(resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
};

Design flaw is doing this in the first place, native promises are useless if you are using Q.

What are the differences between Deferred, Promise and Future in JavaScript?

In light of apparent dislike for how I've attempted to answer the OP's question. The literal answer is, a promise is something shared w/ other objects, while a deferred should be kept private. Primarily, a deferred (which generally extends Promise) can resolve itself, while a promise might not be able to do so.

If you're interested in the minutiae, then examine Promises/A+.


So far as I'm aware, the overarching purpose is to improve clarity and loosen coupling through a standardized interface. See suggested reading from @jfriend00:

Rather than directly passing callbacks to functions, something which
can lead to tightly coupled interfaces, using promises allows one to
separate concerns for code that is synchronous or asynchronous.

Personally, I've found deferred especially useful when dealing with e.g. templates that are populated by asynchronous requests, loading scripts that have networks of dependencies, and providing user feedback to form data in a non-blocking manner.

Indeed, compare the pure callback form of doing something after loading CodeMirror in JS mode asynchronously (apologies, I've not used jQuery in a while):

/* assume getScript has signature like: function (path, callback, context) 
and listens to onload && onreadystatechange */
$(function () {
getScript('path/to/CodeMirror', getJSMode);

// onreadystate is not reliable for callback args.
function getJSMode() {
getScript('path/to/CodeMirror/mode/javascript/javascript.js',
ourAwesomeScript);
};

function ourAwesomeScript() {
console.log("CodeMirror is awesome, but I'm too impatient.");
};
});

To the promises formulated version (again, apologies, I'm not up to date on jQuery):

/* Assume getScript returns a promise object */
$(function () {
$.when(
getScript('path/to/CodeMirror'),
getScript('path/to/CodeMirror/mode/javascript/javascript.js')
).then(function () {
console.log("CodeMirror is awesome, but I'm too impatient.");
});
});

Apologies for the semi-pseudo code, but I hope it makes the core idea somewhat clear. Basically, by returning a standardized promise, you can pass the promise around, thus allowing for more clear grouping.

How an extremely simple implementation of a Promise works

  1. onResolve and onReject have to be bound in order to prevent the executionFunction to apply them with another context or without any context. (if for whatever reason you call resolve or reject with another context, it has to be bound to PromiseSimple, otherwize this will refer to something else or won't refer to anything if no context).

Here is an example that won't work if you don't bind onResolve or onReject :

const makeApiCall = () => {
return new PromiseSimple((resolve, reject) => {
Promise.resolve().then(resolve); // Using native promise here : resolve is called without context and won't work if not bound
});
}

  1. This implementation does block the main thread but you won't probably see it since you're using a setTimeout which delays the execution in the event loop. The true Promise implementation delays the tasks you define in then or catch callback as microtask in the event loop. (There is this very interresting article about browsers event loop).

Hope this helps,

Why do Promise libraries use event loops?

All promise implementations, at least good ones do that.

This is because mixing synchronicity into an asynchronous API is releasing Zalgo.

The fact promises do not resolve immediately sometimes and defer sometimes means that the API is consistent. Otherwise, you get undefined behavior in the order of execution.

function getFromCache(){
return Promise.resolve(cachedValue || getFromWebAndCache());
}

getFromCache().then(function(x){
alert("World");
});
alert("Hello");

The fact promise libraries defer, means that the order of execution of the above block is guaranteed. In broken promise implementations like jQuery, the order changes depending on whether or not the item is fetched from the cache or not. This is dangerous.

Having nondeterministic execution order is very risky and is a common source of bugs. The Promises/A+ specification is throwing you into the pit of success here.

defer function execution without Promise or any JS library

Don't do the queuing yourself. Leave the library loading and dependency resolution to the user. Just expose a function that instantiates your library, the user may call it when library A is ready and get the module with usable methods back.

There even already is a standard on how to do that: Asynchronous Module Definition. (You don't need require.js as a third-party library, you can implement the bare essentials yourself)

If the user wants (needs) to call your library methods before everything is loaded, they can always queue the premature invocations themselves in any manner they want. But usually this should not be necessary.

What is the best way to implement promise in the given scenario?

Looks like you can improve the code if you use promises in more natural way.

First of all if getData returns a Promise then you don't have to pass deferred around, this is considered anti-pattern. You just simply return getInfo().

Another thing, in the sample function if your data might be already available it's convenient to use D.promisify method to return resolved promise with non-promise value:

function getData() {
var data_one;
return getInfo().then(function (resp_one) {
data_one = resp_one;
return getInfo2();
})
.then(function (resp_two) {
return 'prefix' + data_one + resp_two;
});
};

function sample() {
var data = localStorage.getItem('key');
return data ? D.promisify(data) : getData();
}

sample().then(function (data) {
//do something with data.
});


Related Topics



Leave a reply



Submit