|
| 1 | +# Promises. |
| 2 | + |
| 3 | +## What is a Promise? |
| 4 | +A Promise is an object that's representing a potential future value, we just don't know what it will be _yet_. |
| 5 | +When you have a Promise in hands, it promises you to get you a value back or an error. |
| 6 | + |
| 7 | +## Promises vs callbacks. |
| 8 | +Two things are important to note why Promises are a more suitable solutions for asynchronous programming than callbacks. |
| 9 | + |
| 10 | +The first thing is the inversion of inversion of control. Or simply put: restoring control to the calling code. |
| 11 | +When you use callbacks, you invert the control of your asynchronous behaviour to some other external logic (could be your code but it could also be a third party library). |
| 12 | +The issue with inversion of control is that since you're not in control anymore, you let someone else decide _when_ the asynchronous task will be over. |
| 13 | +You also trust that third party code to call your callback in the right time and in the exact amount of time you're expecting. |
| 14 | +Promises give you the control back because _you_ decide when and how the "callback" is called. |
| 15 | + |
| 16 | +```javascript |
| 17 | +// callback based code |
| 18 | +function request(callback) { |
| 19 | + fetch(..., (error, result) => ( |
| 20 | + callback(...) // What if this callback was called too soon? Or never called, or called twice... |
| 21 | + )); |
| 22 | +} |
| 23 | + |
| 24 | +request((error, result) => { |
| 25 | + // ... |
| 26 | +}); |
| 27 | + |
| 28 | +// Promise based code. |
| 29 | +function request() { |
| 30 | + return new Promise(...); |
| 31 | +} |
| 32 | + |
| 33 | +const someRequest = request().then(...); |
| 34 | +``` |
| 35 | + |
| 36 | +The second thing you probably have heard of is "callback hell", when you need to subsequently call more than one asynchronous task. Often, you need to wait until one is done to start another one, which leads to deep nesting. It also makes it hard to run concurrent async code and wait until everything is done to keep going on something else. |
| 37 | + |
| 38 | +```javascript |
| 39 | +asyncTaskOne((error, result) => { |
| 40 | + asyncTaskTwo((error, result) => { |
| 41 | + asyncTaskThree((error, result) => { |
| 42 | + // .... |
| 43 | + }); |
| 44 | + }); |
| 45 | +}); |
| 46 | +``` |
| 47 | + |
| 48 | +This gets hard to read and to reason about extremely quickly. Nesting often means unnecessary complexity in the code. |
| 49 | +Instead, a promise based code would look like |
| 50 | + |
| 51 | +```javascript |
| 52 | +asyncTaskOne() |
| 53 | + .then(asyncTaskTwo) |
| 54 | + .then(asyncTaskThree); |
| 55 | + |
| 56 | +// Or even better, if you the tasks are independent |
| 57 | +Promise.all([ |
| 58 | + asyncTaskOne, |
| 59 | + asyncTaskTwo, |
| 60 | + asyncTaskThree |
| 61 | +]).then([resultOne, resultTwo, resultThree] => ...); |
| 62 | +``` |
| 63 | + |
| 64 | +## How do Promises work? |
| 65 | +Promises are immutable objects that can have three states: |
| 66 | +- `Pending` |
| 67 | +- `Fulfilled` |
| 68 | +- `Rejected` |
| 69 | + |
| 70 | +It always starts with the `pending` state (that's what you get right away after creating the promise) and will move to `fulfilled` or `rejected`. Once the state changed, it cannot change anymore due to their immutable behaviour. |
| 71 | + |
| 72 | +- `Fulfilled` means the Promise has resolved with a value that is not an error. |
| 73 | +- `Rejected` means the Promise has resolved with an error (whatever you were doing didn't work). |
| 74 | + |
| 75 | +Based on their next state, a Promise will call one of its methods. |
| 76 | +A Promise is what is called a "thenable", which means it has a `then` method on it. That method is being called |
| 77 | +whenever the Promise gets resolved (error or not). |
| 78 | + |
| 79 | +`then` takes two arguments (two functions called `onFulfill` and `onReject`), the second one being optional. |
| 80 | +The first function will be called if the Promise gets fulfilled (resolved but didn't error out). |
| 81 | +The second argument will be called (if provided) if the Promise gets rejected (resolved with an error). |
| 82 | + |
| 83 | +Let's take a look at a _potential_ `then` signature: |
| 84 | +```javascript |
| 85 | +function then(onFulfill, onReject) { } |
| 86 | +``` |
| 87 | + |
| 88 | +```javascript |
| 89 | +Promise.resolve('hello world') |
| 90 | + .then(console.log); // 'hello world' |
| 91 | +``` |
| 92 | + |
| 93 | +```javascript |
| 94 | +Promise.reject('bad') |
| 95 | + .then( |
| 96 | + (result) => { |
| 97 | + // Never gets here... |
| 98 | + }, |
| 99 | + (error) => { |
| 100 | + console.error(error); // 'bad' |
| 101 | + } |
| 102 | + ) |
| 103 | +``` |
| 104 | + |
| 105 | +Because it's optional, the second argument can be omitted. Although, if you don't provide an error handler, |
| 106 | +javascript will swallow the error and never tell you what happened. In the future, rejected Promise will exit the node process with a non 0 code. Which is bad. |
| 107 | + |
| 108 | +Last but not least, there is another way to deal with errors in Promises. |
| 109 | +Promises have a `catch` method, which can act as an error-handler as well. |
| 110 | + |
| 111 | +```javascript |
| 112 | +Promise.reject('bad') |
| 113 | + .catch(error => console.error('Catching the error...')); |
| 114 | +``` |
| 115 | + |
| 116 | +There are some differences between using a second error handler in the `then` or using `catch`. |
| 117 | +Consider the following code: |
| 118 | + |
| 119 | +```javascript |
| 120 | +Promise.resolve(true) |
| 121 | + .then( |
| 122 | + result => Promise.reject(false), |
| 123 | + error => console.error('Couldn\'t catch the rejected promise') // This is never be called because the Promise already fulfilled. |
| 124 | + ) |
| 125 | + .catch(error => console.error('Caught the rejected Promise')) // This is called because `then` returned a rejected promised |
| 126 | + .then(() => Promise.reject(false)) |
| 127 | + .then( |
| 128 | + result => console.log('This is never called'), |
| 129 | + error => console.error('Caught the rejected Promise') |
| 130 | + ) |
| 131 | + .catch(error => console.error('This is never called')) // Never called because we already handled the error before. |
| 132 | +``` |
| 133 | + |
| 134 | +## All about `then` and how to use it efficiently. |
| 135 | +As we said earlier, Promises are "thenables", so they have a `then` method on them. |
| 136 | +This is a very powerful function because anything you return from a `then` function is converted to a Promise (If not already one) that will resolve in whatever you're returning. |
| 137 | + |
| 138 | +If you return something that is not a Promise, `then` will transform it to a Promise that will **resolve** with whatever your returned earlier. |
| 139 | + |
| 140 | +```javascript |
| 141 | +Promise.resolve() |
| 142 | + .then(() => 'hello') |
| 143 | + .then(console.log) // 'hello' |
| 144 | + |
| 145 | +``` |
| 146 | + |
| 147 | +I've highlighted the word _resolve_ above. It is very important to understand that the Promise created by `then` will resolve (can be fulfilled or rejected) the value. |
| 148 | +If you're trying to resolve a rejected Promise (so if you return a rejected promise), the returned Promise from `then` will also be rejected. |
| 149 | + |
| 150 | +```javascript |
| 151 | +Promise.resolve() |
| 152 | + .then(() => Promise.reject()) |
| 153 | + .then(() => ...) // Never called! |
| 154 | + .catch(error => console.error(error)); // Called because we've returned a rejected Promise. |
| 155 | +``` |
| 156 | + |
| 157 | +Another interesting fact about `then` is that if you return a Promise, the main Promise will wait and only resolve once the returned Promise (from the `then`) is resolved. |
| 158 | + |
| 159 | +```javascript |
| 160 | +Promise.resolve() |
| 161 | + .then(() => { |
| 162 | + return new Promise(resolve => { |
| 163 | + setTimeout(resolve, 2000); |
| 164 | + }); |
| 165 | + }) |
| 166 | + .then(() => { |
| 167 | + return new Promise(resolve => { |
| 168 | + setTimeout(resolve, 2000); |
| 169 | + }); |
| 170 | + }) |
| 171 | + .then(() => { |
| 172 | + // Will be called only after 4 seconds |
| 173 | + }); |
| 174 | + |
| 175 | +// Another example |
| 176 | +function getPromise() { |
| 177 | + return new Promise(resolve => setTimeout(resolve, 2000)) |
| 178 | + .then(() => new Promise(resolve => setTimeout(resolve, 2000))); |
| 179 | +} |
| 180 | + |
| 181 | +getPromise() |
| 182 | + .then(() => { |
| 183 | + // Will only be called after 4 seconds. |
| 184 | +}); |
| 185 | +``` |
| 186 | +## Ressources |
| 187 | + |
| 188 | +- [MDN Promises](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) |
0 commit comments