Understanding JavaScript Promise Object

Understanding JavaScript promise object

Promise execution is asynchronous, which means that it's executed, but the program won't wait until it's finished to continue with the rest of the code.

Basically, your code is doing the following:

  1. Log 'Hi'
  2. Create a promise
  3. Execute the promise
  4. Log 'zami'
  5. Promise is resolved and logs 'There'.

If you want it to print 'Hi there, zami', you will have to

myPromise.then(function (result) {
// Resolve callback.
console.log(result);
console.log('zami');
}, function (result) {
// Reject callback.
console.error(result);
});

Having trouble fully understanding Promises in Javascript

Have you ever been to a pizza restaurant, where you order your pizza and then you don't get the pizza immediately, but you get a buzzer device that will warn you when your pizza is ready? That's exactly what a Promise is.

The function that you pass to the constructor of Promise is the cook that is going to prepare the pizza. The cook takes two parameters, which are functions. When the cook has finished the pizza, it will call the resolve function. If something goes wrong and the pizza cannot be finished, it will call the reject function.

Who calls resolve and reject?

The function that you pass to the constructor of Promise (the cook) does. It's right there in your code:

if (bool) {
resolve('Pass'); // Call the resolve function
} else {
reject('Fail'); // Call the reject function
}

You supply the actual functions to call by calling then(...) and catch(...) on the Promise object.

p.then(function(val) {    // <= This function is the actual resolve function
console.log(val);
}).catch(function(val) { // <= This function is the actual reject function
console.log(val);
});

It might be easier to understand by using a concrete example like with the cook and pizza than when you use abstract concepts:

function pizzaCook(resolve, reject) {
let bool = false;

if (bool) {
resolve('Pizza is ready!');
} else {
reject('Sorry, the oven is broken!');
}
}

var p = new Promise(pizzaCook);

function letsEat(msg) {
console.log('Great, I can eat now, the cook said: ' + msg);
}

function stayHungry(msg) {
console.log('Oh no, I will stay hungry, because: ' + msg);
}

p.then(letsEat).catch(stayHungry);

What is a promise object?

A promise is a new Object type of EcmaScript 6 (ES6), for which there are numerous polyfills and libaries (i.e. implementations for ES5 JavaScript engines), and allows (among other benefits) to get out of the infamous callback hell, and to write and read asynchronous code easily.

A promise can have one (and only one) of these three status:

  • pending
  • fulfilled
  • rejected

If the promise is rejected or fulfilled, it also has a settled status.

Basically, it is an object that has a then property (amongst others), which is a function that takes at least one function as a parameter, and can take two: the first one will be invoked if the promise returns a fulfilled status, and the second one will be invoked if the promise returns a rejected status

The then function returns itself another promise, so promises are chainable.

Promise objects are rather more complicated than what I just wrote here, but it was just to give you a start.

BTW, you may have used a promise-like object (note the -like suffix) if you use jQuery : $.ajax() returns a promise-like object (those are called thenables) that has a done (and a then) property which is a function that accepts a function as the parameter that seems like a fulfilled function (which normally takes only one argument). Promise objects also may have a done function property (not standardised, AFAIK, but almost all the polyfills and the libraries implement it), which acts like a then function, only it does not return a promise (hence the name: if you are done with the promise, then use done(), but if you need to do somehting with the result of the promise, use then()).

e.g.: you may have seen or written something like this:

$.ajax({url: '/path/to/html/piece'})
.done(function(data) {
$('whateverSelector').html(data);
});

But what jQuery calls promises, even if they are thenables, does not fulfill the promise spec.

difficult to understand promise and asycn... ;

We have Promise and Observables to deal with the asynchronous nature of functions or app and yes Async always returns a promise while obervables can be returned by both synchronous and Asynchronous functions. A promise is followed by two keywords,

const promise = FooPromise((resolve,reject) = > {
revolve("i am resolve");
reject("i am reject");
)

after this we define what to do if our promise is resolved or rejected by these keywords respectively.

.then((resolveMessage) => {
console.log(resolveMessage);
}
.catch((catchMessage) => {
console.log(CatchMessage);
}

How JavaScript promises work behind the scenes

The following is a simplified implementation of the built-in Promise class. catch and finally have not been implemented.

The function supplied to the Promise constructor is called the executor function, and is invoked immediately and synchronously.

Every promise has a method .then, enabling the chaining of promises.

Functions supplied to .then are always invoked asynchronously on a microtask (note use of queueMicrotask below).

Every time .then is called, a new promise is created and returned.

.then can be called more than once on the same promise, creating a multicast of the result of the promise, and a branching of the promise chain.

A promise can be in one of three states: pending, fulfilled, or rejected. State transitions are unidirectional: you cannot move from fulfilled or rejected, back to pending.

If a promise is resolved with another promise, then the two promise chains are joined and the outer promise takes on the status of the inner promise (which could be pending), until the inner promise resolves.

function Promise(executor) {  if (!executor) throw "Promise executor undefined"  let status = "pending", value, thenQ = []
const then = onFulfilled => { let resolver // This ensures control does not move to later promises // until prior promises have been resolved. const nextPromise = new Promise(resolve => (resolver = resolve)) // More than one "then" can be registered with each promise. thenQ.push((...args) => resolver(onFulfilled(...args))) return nextPromise }
// We check if the result is a "thenable"; if so, we treat // it as an inner promise, otherwise we simply fulfil with // the result. const resolve = result => result?.then ? result.then(fulfil) : fulfil(result)
// When a promise has been fulfilled, its "thens" can be run. const fulfil = result => (status = "fulfilled", value = result, executeThens(value))
// "Thens" are run asynchronously, on a microtask. const executeThens = value => queueMicrotask(() => thenQ.forEach(el => el(value)))
// The executor is run synchronously. executor(resolve)
return { then, get status() { return status }, get value() { return value } }}
// Chainingnew Promise(resolve => { console.log('Waiting for step 1...') setTimeout(() => resolve("One, two..."), 1500)}).then(result => new Promise(resolve => { console.log('Waiting for step 2...') setTimeout(() => resolve(`${result}three, four`), 1500)})).then(result => console.log(`Chaining result: ${result}.`))
// Branchingconst p = new Promise(resolve => { console.log('Waiting for step a...') setTimeout(() => resolve("Alpha, Bravo..."), 1500)})
p.then(result => new Promise(resolve => { console.log('Waiting for step b1...') setTimeout(() => resolve(`${result}Charlie, Delta`), 1500)})).then(console.log)
p.then(result => { console.log('Waiting for step b2...') return `${result}Echo, Foxtrot`}).then(console.log)

Understanding JS Promises

About Promise resolution

The thing you're witnessing here is called recursive thenable resolution. The promise resolution process in the Promises/A+ specification contains the following clause:

onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x)

The ES6 promise specification (promises unwrapping) contains a similar clause.

This mandates that when a resolve operation occurs: either in the promise constructor, by calling Promise.resolve or in your case in a then chain a promise implementation must recursively unwrap the returned value if it is a promise.

In practice

This means that if onFulfilled (the then) returns a value, try to "resolve" the promise value yourself thus recursively waiting for the entire chain.

This means the following:

promiseReturning().then(function(){
alert(1);
return foo(); // foo returns a promise
}).then(function(){
alert(2); // will only run after the ENTIRE chain of `foo` resolved
// if foo OR ANY PART OF THE CHAIN rejects and it is not handled this
// will not run
});

So for example:

promiseReturning().then(function(){
alert(1);
return Promise.resolve().then(function(){ throw Error(); });
}).then(function(){
alert("This will never run");
});

And that:

promiseReturning().then(function(){
alert(1);
return Promise.resolve().then(function(){ return delay(2000); });
}).then(function(){
alert("This will only run after 2000 ms");
});

Is it a good idea?

It's been the topic of much debate in the promises specification process a second chain method that does not exhibit this behavior was discussed but decided against (still available in Chrome, but will be removed soon). You can read about the whole debate in this esdiscuss thread. This behavior is for pragmatic reasons so you wouldn't have to manually do it.

In other languages

It's worth mentioning that other languages do not do this, neither futures in Scala or tasks in C# have this property. For example in C# you'd have to call Task.Unwrap on a task in order to wait for its chain to resolve.

How do promises work in JavaScript?

A promise is basically an object with two methods. One method is for defining what to do, and one is for telling when to do it. It has to be possible to call the two methods in any order, so the object needs to keep track of which one has been called:

var promise = {
isDone: false,
doneHandler: null,
done: function(f) {
if (this.isDone) {
f();
} else {
this.doneHandler = f;
}
},
callDone: function() {
if (this.doneHandler != null) {
this.doneHandler();
} else {
this.isDone = true;
}
}
};

You can define the action first, then trigger it:

promise.done(function(){ alert('done'); });
promise.callDone();

You can trigger the action first, then define it:

promise.callDone();
promise.done(function(){ alert('done'); });

Demo: http://jsfiddle.net/EvN9P/

When you use a promise in an asynchronous function, the function creates the empty promise, keeps a reference to it, and also returns the reference. The code that handles the asynchronous response will trigger the action in the promise, and the code calling the asynchronous function will define the action.

As either of those can happen in any order, the code calling the asynchronous function can hang on to the promise and define the action any time it wants.



Related Topics



Leave a reply



Submit