Knowing how Promise
works in javascript will boost your development skill exponentially. Here I will share:
- The basic of
Promise
- How to use
then
-
catch
and error handling
I promise you this will not be as hard as you think! 🤓
What is a Promise
?
Per MDN:
The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.
In beginner's term, a Promise a JS object. It doesn't have a value the moment it is declared, but it will at some time in the future. This value is either resolved or rejected.
Suppose you ordered a new hoodie from dev.to. When you purchased it, it is technically yours, but it's not quite yours because you don't have it physically, just a promise that it will be delivered. At any moment, the hoodie's status is either: arrived, on delivery, or lost. Note that even when the hoodie arrived, you may decide to reject it if it is not the right size/ you got a different product instead.
Just like the hoodie, Promise has 3 states at any time: fulfilled, rejected, pending.
Using new Promise
Let's get started using promise!
let foo = new Promise((resolve, reject) => {resolve('foo')})
foo.then(value => console.log(value) // foo
We can "shorthand" it by using Promise.resolve
. Below is equivalent to above:
let foo = Promise.resolve('foo')
foo.then(value => console.log(value)) // foo
Promise + setTimeout
Let's add timeout to mimic async:
let promise1 = new Promise((resolve, reject) => {
setTimeout(function() {
resolve('foo');
}, 2000)
})
promise1.then(val => console.log(val))
console.log("I promise I'll be first!")
// I promise I'll be first!
// ... 2 secs later ¯\_(ツ)_/¯
// foo
Note the order of logs.
Some notes:
- Once promise is declared (
new Promise(...)
), time starts ticking. -
promise1
itself is a promise object. You can see it on console:promise1 // Promise {<resolved>: "foo"}
- You can access "foo" using
then
(or other async methods, but that's another article). My point is, you can't justconsole.log(promise1)
in global scope and expect to access string "foo". You need to putconsole.log()
insidethen
.
Continuous chaining
Promise can be chained, allowing you to make serial promises.
let hello1 = new Promise(resolve => resolve("hello1"))
hello1.then(val1 => {
console.log(val1);
return "hello2"
}).then(val2 => {
console.log(val2);
return "hello3"
}).then(val3 => {
console.log(val3)
})
// hello1
// hello2
// hello3
Here you'll notice that after my hello1's then
, I return
"hello2". This "hello2" is the value of val2. The same with the second then
, it returns "hello3" and it is the value of val3. Note that to pass on argument in promise chain, the previous then
must have a return value. If you don't return value, the next then will have no argument.
Here is what I mean:
hello1.then(val1 => {
console.log(val1);
return "hello2"
}).then(val2 => {
console.log(val2); // no return
}).then(val3 => {
console.log(val3); // val3 is undefined
})
// hello1, hello2, undefined
Chain continues, but val3 has no value because the previous chain fails to provide return value.
API call
I will only briefly touch making API call with Promise because the concept is the similar with setTimeout
. Let's use fetch
because it is built-in (and you can play with it on chrome console!). This code from typicode site:
let fetchTodo = fetch('https://jsonplaceholder.typicode.com/todos/1')
fetchTodo // Promise {<pending>}
fetchTodo
.then(response => response.json())
.then(json => console.log(json))
When we first make API call with fetchTodo = fetch('https://jsonplaceholder.typicode.com/todos/1')
, it returns a Promise.
We now how to deal with promise object - just then
it!
Catching Error and Rejection Handling
Remember the 2nd argument of new Promise? Suppose we don't like the result of our async operation. Instead of resolving it, we can reject it.
let fooReject = new Promise((resolve, reject) => {reject('foo rejected')})
fooReject // Promise {<rejected>: "error foo"}
It is really good habit to catch errors in promise. As a rule of thumb 👍:
Every time we use
then
from now on, always, always have acatch
let foo = new Promise((resolve, reject) => {reject('error foo')})
foo.then(value => console.log(value)).catch(err => console.log(err)) //gotta catch 'em all!
foo //error foo
What just happened?
Let's compare it if we had only put then
without catch
foo = new Promise((resolve, reject) => {reject('error foo')})
foo.then(val => console.log(val))
// Promise {<rejected>: "error foo"}
Ah, on my chrome console, it is complaining because an error is uncaught. We need to catch the error. Let's catch it!
foo.then(val => console.log(val)).catch(err => console.log(err)) // error foo
Now we see a cleaner log!
Different rejection method
You may ask, "hey man, what if I have a chain:"
let promise1 = new Promise(fetchSomeApi);
promise
.then(processApi)
.then(fetchApi2)
.then(processApi2)
.catch(handleCommonError)
"and I want to do something different for processApi
and let handleCommonError to handle the remaining errors?"
Luckily there is more than one way to catch error! then
takes second argument.
Recall our first code above: let foo = new Promise((resolve, reject) => {resolve('foo')})
. We will use reject
for custom error handling.
You can do something like this:
promise
.then(processApi)
.then(fetchApi2, customErrorHandling)
.then(processApi2)
.catch(handleCommonError)
Should something go wrong during processApi
, the result will go to .then(fetchApi2, CustomErrorHandling)
line. When then
sees that it sees an error/ reject, instead of firing fetchApi2
, it fires customErrorHandling
.
It is a good practice to still have catch
even if you have reject callback.
More resolve, reject, catch examples
Resolved example:
let successFoo = new Promise((resolve, reject) => {resolve('foo')})
.then(val => console.log(`I am resolved ${val}`), err => console.log(`I am rejected ${err}`))
.catch(err => console.log("HELLO ERROR"))
// I am resolved foo
Rejected example:
let rejectFoo = new Promise((resolve, reject) => {reject('error foo')})
.then(val => console.log(`I am resolved ${val}`), err => console.log(`I am rejected ${err}`))
.catch(err => console.log("HELLO ERROR"))
// I am rejected error foo
Note that it never reach catch
. The second argument handles this. If you want to reach catch, jus don't pass 2nd argument:
let catchFoo = new Promise((resolve, reject) => {reject('error foo')})
.then(val => console.log(`I am resolved ${val}`)).catch(err => console.log("HELLO ERROR"))
// HELLO ERROR
And that's all folks! Clearly not everything is covered but the basic cases. My goal is not to make you Promise gurus, but good enough to get you started so you can do more fancy stuff. Hopefully it all makes sense!
There are more into Promise that is not mentioned, I would suggest looking up all()
, finally()
, and race()
. I promise (😎), it's worth your time!
Thanks for reading, as always, please feel free let me know if you see an error/ typo/ mistakes.
Happy hacking!
Top comments (0)