Javascript – Promises

What is a promise, different states of promises

Promises are objects that represent the eventual completion or failure of an asynchronous operation.

Create a Javascript Promise

A promise in Javascript is exactly what it sounds like – you use it to make a promise to do something, usually asynchronously. When the task completes. you either fulfill your promise or fail to do so.

Promise is a contructor function, so you need to use the new keyword to create one. It takes a function, as its argument, with two parameters – resolve and reject. These are methods used to determine the outcome of the promise.

The syntax looks like this :

consy myPromise = new Promise((resolve, reject) => {
	console.log('inside promise');
});
JavaScript

Complete a Promise with resolve and reject

A promise has three states : pending, fulfilled and rejected

The promise you created in the last challenge is forever stuck in the pending state because you did not add a way to complete the promise. The resolve and reject parameters given to the promise argument are used to do this. resolve is used when you want your promise to succeed, and reject is used when you want it to fail.

These are methods that take an argument, as seen below.

const myPromise = new Promise((resolve, reject) => {
	if (condition here) {
		resolve("promise was fulfilled");
	} else {
		reject("promise was rejected");
	}
});
JavaScript

The example above uses strings for the argument of these functions, but it can really be anything. Often it might be an object that you would use data from, to put on your website or elsewhere.

const fetchData = () => {
	return new Promise((resolve, reject) => {
		// Fetch data from API and resolve reject accordingly 
		let data = someAPI(); 
		if (data) { // response from server
			console.log('we got the data');
			resolve(data);
		} else {
			console.log('data not received');
			reject('data fetch error');
		}
	}
}
JavaScript

Handle a fulfilled promise with then

Promises are most useful when you have a process that takes an unknown amount of time in your code (i.e something asynchronous), often a server request.

When you make a server request it takes some amount of time, and after it completes – you usually want to do something with the response from the server.

This can be acheived by using then method. The then method is executed immediately after you promise is filled with resolve.

Here’s an example :

myPromise.then(result => {
  // promise is resolved
	console.log('do something with the data');
	return result;
})
JavaScript

Handle a rejected promise with catch

catch is the method used when your promise has been rejected. It is executed immediately after a promise’s reject method is called. Here’s the syntax :

myPromise.catch(error => {
	// promise is rejected
	console.log('data not received');
	throw new error;
});
JavaScript

Example

Imagine a function, createAudioFileAsync() which asynchronously generates a sound file given a configuration record and two callback functions, one called if the audio file is successfully created, and the other called if an error occures.

function successCallback(result) {
	console.log(`Audio file ready at URL: ${result}`);
}

function failureCallback(error) {
	console.error(`Error generating audio file: ${error}`);
}

createAudioFileAsync(audioSettings, successCallback, failureCallback);
JavaScript

If createdAudioFileAsync() were rewritten to return a promise, you would attach your callbacks to it instead:

createAudioFileAsync(audioSettings).then(successCallback, failureCallback);
JavaScript

This convention has several advantages. We will explore each one.

Promise Chaining

With promises, we can now execute two or more asynchronouse operations back to back, where each subsequent operation starts when the previous operation succeeds, with the result from the previous step.

Earlier, before promises, doing several asynchronous operations in a row lead to the classic Callback Hell

What is “callback hell” ?

Asynchronous Javascript, or javascript that uses callbacks, is hard to get right.. A lot of code ends up looking like this:

doSomething(function (result) {
	doSomethingElse(result, function (newResult) {
		doThirdThing(newResult, function (finalResult) 	
		{
		  console.log(`Got the final results: ${finalResult}`);
		}
	}, failureCallbacl);
}, failureCallback);
JavaScript

The cause of callback hell is when people try to write Javascript in a way where execution happens visually from top to bottom.

Lots of people make this mistake! In other languages like C, Ruby or Python there is the expectation that whatever happens on line 1 will finish before the code on line 2 starts running and so on down the file. As you will learn , Javascript is different

With Promise Chaining we can create longer chains of processing, where each promise represents the completion of one asynchronous step in the chain

// Promise Chaining 
doSomething()
	.then(function (result) {
		return doSomethingElse(result);
	})
	.then(function (newResult) {
		return doThirdThing(newResult);
	})
	.then(function (finalResult) {
		console.log(`Got the final result ${finalResult}`);
	})
	.catch(failureCallback);
JavaScript

You might see this expressed with arrow functions instead :

// Promise chaining with arrow functions
doSomething()
	.then((result) => doSomethingElse(result))
	.then((newResult) => doThirdThing(newResult))
	.then((finalResult) => {
		console.log(`Got the final results ${finalResult}`);
	})
	.catch(failureCallback);
JavaScript

Avoid floating promises

It is important to always returen promises from then callbacks, even if the promise always resolves to undefined.

If the previous handler started a promise but did not return it, there’s no track its settlement anymore, and the promise is said to be “floating”.

doSomething()
	.then((url) => {
		// Missing `return` keyword in front of fetch(url).
		fetch(url);
	})
	.then((result) => {
		// result is undefined, because nothing is returned from the previous handler
		
		// There's no way to know the return value of the fetch() call anymore, or whether it succeeded at all
	});
JavaScript

By returning the result of the fetch call (which is a promise), we can both track its completion and receive its value when it completes.

doSomething()
	.then((url) => {
		return fetch(url); // return keyword added
	})
	.then((result) => {
		// result is a Response object
	});
JavaScript

Floating promises could be worse, if you have race conditions – If the promise from the last handler is not returned, the next then handler will be called early, and any value it reads may be incomplete.

const listOfIngredients = [];

doSomething()
	.then((url) => {
		// Missing `return` keyword infront of fetch(url).
		fetch(url)
			.then((res) => res.json())
			.then((data) => {
				listOfIngredients.push(data);
			});
	})
	.then(() => {
		console.log(listOfIngredients);
		// listOfIngredients will always be [], because the fetch request hasn't completed yet
	});
JavaScript

Therefore, as a rule of thumb, whenever your operation encounters a promise, return it and defer its handling to the next then handler.

const listOfIngredients = [];

doSomething()
  .then((url) => {
    // `return` keyword now included in front of fetch call.
    return fetch(url)
      .then((res) => res.json())
      .then((data) => {
        listOfIngredients.push(data);
      });
  })
  .then(() => {
    console.log(listOfIngredients);
    // listOfIngredients will now contain data from fetch call.
  });
JavaScript

Even better, you can flatten the nested chain into a single chain, which is simpler and makes error handling easier.

doSomething()
  .then((url) => fetch(url))
  .then((res) => res.json())
  .then((data) => {
    listOfIngredients.push(data);
  })
  .then(() => {
    console.log(listOfIngredients);
  });
JavaScript

Coming soon :

  • Using async/await
  • Chaining after a catch
  • Promise rejection events
  • Promise Composition (Promise.all(), allSettled(), any(), race()
  • Promise Cancellation
  • Task queues vs microtasks
  • Error handling

Ref :

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *