Promises in ES6

Promises have been introduced natively as a part of ES6 now. In this blogpost hopefully I will try to explore all the aspects of Promises.

So what is a Promise?

Promises are merely a prettier way of writing code which makes an asynchronous code look like synchronous.

// Usual way of XHR 
$.get("script.php", function(data) {
	console.log('Your account has been created successfully!');
});

// With jQuery Promises
var request = $.ajax({
	url: "script.php",
	method: "POST",
	data: {
		'user': formData.username,
		'pwd': formData.password
	}
});

request.done(function(msg) {
	console.log('Your account has been created successfully!');
});

request.fail(function(jqXHR, textStatus) {
	console.log('Looks like the account already exists');
});

Promises are very much similar to event handling but there are some minor differences. A promise can only succeed or fail once. If a promise has succeeded or failed and you later add a success/failure callback, the correct callback will be called, even though the event took place earlier

Promises have been around for a while in the form of libraries, like Q and when which have a standardised behaviour called Promises/A+ same as ES6 Promises. jQuery also has a Promise type called deferreds but they are not Promises/A+ compliant.

var promise = new Promise(function(resolve, reject) {

	// do your async thing
	var req = new XMLHttpRequest();
	req.open('GET', url);

	req.onload = function() {
		if (req.status == 200) {
			resolve(req.response); // resolve here
		} else {
			reject(Error(req.statusText)); // reject here
		}
	};

	req.onerror = function() {
		reject(Error("Network Error")); // reject here
	};

	req.send();
});

promise.then(function(result) { // if Resolved
	console.log(result); // Response
}, function(err) { // if Rejected
	console.log(err); // Error
});

Chaining - when you have mutiple Ajax to do.

var promise = new Promise(function() {
	if ( /* condition */ ) {
		resolve(1);
	} else {
		reject(-1);
	}
});

promise.then(function(data) {
	data++;
	return data;
}, function(err) {
	err--;
	return err;
}).then(function(data) {
	data++;
	return data;
}, function(err) {
	err--;
	return err;
})

Use chaining as middlewares

get('story.json').then(function(response) {
	return JSON.parse(response);
}).then(function(response) {
	console.log("Yey JSON!", response);
});

// `then` is smarter than this so if you return a promise
// the next `then` waits for that `promise` to get resolved
getJSON('story.json').then(function(story) {
	return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
	console.log("Got chapter 1!", chapter1);
});

// case 1:
get('story.json').then(function(response) {
	console.log("Success!", response);
}, function(error) {
	console.log("Failed!", error);
});

// case 2: Suger for reject: `catch`
get('story.json').then(function(response) {
	console.log("Success!", response);
}).catch(function(error) {
	console.log("Failed!", error);
});

// case 3:
get('story.json').then(function(response) {
	console.log("Success!", response);
}).then(undefined, function(error) {
	console.log("Failed!", error);
});

Notice above three cases, only 2nd and 3rd are equivalent. If there is an error in resolve callback it will go to the next then(undefined, function() {}) and catch which is not the case in 1st. In case 1: only will execute no matter what!

Check out below example, you will notice one interesting thing if for some reason asyncThing1, asyncThing2 or asyncThing3 fails then it will go to the immediate catch block

asyncThing1().then(function() {
	return asyncThing2();
}).then(function() {
	return asyncThing3();
}).catch(function(err) {
	return asyncRecovery1();
}).then(function() {
	return asyncThing4();
}, function(err) {
	return asyncRecovery2();
}).catch(function(err) {
	console.log("Don't worry about it");
}).then(function() {
	console.log("All done!");
});


// Implicit error 
var promise = new Promise(function(resolve, reject) {
	resolve(JSON.parse("This ain't JSON"));
});

promise.then(function(data) {
	console.log("It worked!", data); // expected to come here but
}).catch(function(err) {
	console.log("It failed!", err); // comes here
});

// I think I understood everything, now I can do things on my own.
story.chapterUrls.forEach(function(chapterUrl) {
	// Fetch chapter
	getJSON(chapterUrl).then(function(chapter) {
		// and add it to the page
		addHtmlToPage(chapter.html);
	});
});

Not so quick! You got to learn the art of sequence.

// Start off with a promise that always resolves
var sequence = Promise.resolve();

// Loop through our chapter urls
story.chapterUrls.forEach(function(chapterUrl) {
	// Add these actions to the end of the sequence
	sequence = sequence.then(function() {
		return getJSON(chapterUrl);
	}).then(function(chapter) {
		addHtmlToPage(chapter.html);
	});
});

Do you smell something wrong with the above approach ? What we are doing above? - We are making one AJAX call when the response of previous one is received but we can do much better than that!

Browsers are pretty good at downloading multiple things at once, so we’re losing performance by downloading chapters one after the other. What we want to do is download them all at the same time, then process them when they’ve all arrived.

var arrayOfPromises = [
	getPromise('http://api.openweathermap.org/data/2.5/weather?lat=35&lon=139'),
	getPromise('http://api.icndb.com/jokes/random'),
	getPromise('http://api.openweathermap.org/data/2.5/weather?lat=135&lon=19'),
	getPromise('http://api.icndb.com/jokes/random'),
	getPromise('http://api.openweathermap.org/data/2.5/weather?lat=15&lon=29'),
	getPromise('http://api.icndb.com/jokes/random'),
	getPromise('http://api.openweathermap.org/data/2.5/weather?lat=13&lon=90'),
	getPromise('http://api.icndb.com/jokes/random'),
	getPromise('http://api.icndb.com/jokes/random')
];

Promise.all(arrayOfPromises).then(function(arrayOfResults) {

	console.log('All Promises got resolved!'); // All Promises got resolved in same order they were called!
	console.log(arrayOfResults);

}).catch(function(err) {

	console.log('Atleast one of them died on the way!'); // Atleast one of them died on the way!
});


// Description : Whatever resolves/rejects first will be returned
var p1 = new Promise(function(resolve, reject) {

	setTimeout(resolve, 500, "one");
});

var p2 = new Promise(function(resolve, reject) {

	setTimeout(resolve, 100, "two");
});

Promise.race([p1, p2]).then(function(value) {
	console.log(value);
}); // "two"

var p3 = new Promise(function(resolve, reject) {

	setTimeout(resolve, 100, "three");
});

var p4 = new Promise(function(resolve, reject) {

	setTimeout(reject, 500, "four");
});

Promise.race([p3, p4]).then(function(value) {

	console.log(value); // "three"
}, function(reason) {
	// Not called
});

var p5 = new Promise(function(resolve, reject) {

	setTimeout(resolve, 500, "five");
});

var p6 = new Promise(function(resolve, reject) {

	setTimeout(reject, 100, "six");
});

Promise.race([p5, p6]).then(function(value) {
	// Not called              
}, function(reason) {

	console.log(reason); // "six"
});

Recipe of the day: Generators and Promises

Now comes the Generators. It was not covered in detail in my other post about ES6. Let’s see what does it bring to the table.

var path = 'http://www.html5rocks.com/en/tutorials/es6/promises/';

var asyncFun = function(someUrl, resolve, reject) {

	$.get(someUrl)
		.done(function(response) {

			if (response) {
				resolve(response);
			} else {
				reject('Empty Response');
			}
		})
		.error(function(response) {

			reject('Empty Response');

		});

};

var getPromise = function(someUrl) {

	return new Promise(function(resolve, reject) {

		asyncFun(someUrl, resolve, reject); // do some async

	});
};

var addHtmlToPage = function(html) {

	console.log('Below HTML added to page!');
	console.log(html);
};

function* generatorFun() {

	var path = 'http://www.html5rocks.com/en/tutorials/es6/promises/';
	var stories = yield getPromise(path + 'story.json');

	stories.chapterUrls.forEach(function(chapterUrl) {

		yield getPromise(path + chapterUrl);

	});

}

function runGenerator() {
	var it = generatorFun();

	it.next().then(function(stories) {
		console.log(stories);
		return it.next(stories);
	}).then(function(chapter) {
		console.log(chapter);
		return it.next();
	}).then(function(chapter) {
		console.log(chapter);
		return it.next();
	}).then(function(chapter) {
		console.log(chapter);
		return it.next();
	}).then(function(chapter) {
		console.log(chapter);
		return it.next();
	}).then(function(chapter) {
		console.log(chapter);
	});

}

runGenerator();

Read more here