Skip to content

Commit 6366f60

Browse files
committed
Starting to add some new good practices to the JS repo. Added a whole document about Promises.
1 parent f682964 commit 6366f60

2 files changed

Lines changed: 202 additions & 0 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ _Javascript style guide we use at [Transit](https://transitapp.com/)._
44
## Content
55
- [Airbnb Javascript style guide](#airbnb-javascript-style-guide)
66
- [Exceptions](#exceptions)
7+
- [Naming conventions](#naming-conventions)
8+
- [ES2015 and beyond](#es2015-and-beyond)
79

810
## Airbnb Javascript style guide
911
Our styles are based on the excellent [javascript guide from Airbnb](https://github.com/airbnb/javascript) with a few exceptions.
@@ -100,3 +102,15 @@ foreign name should be prefixed for disambiguation, or even both.
100102
With some reasonable limits, one shouldn't be afraid to have long variable name, and be more afraid
101103
of meaningless names. For example:
102104
- `apiStopByStopIdByTripHeadsignByRouteId` should be still preferred as `dict` or `apiStops`.
105+
106+
## ES2015 and beyond
107+
108+
### Promises
109+
Dealing with asynchronous code can be tricky sometimes. To simplify and improve readability, one must stop using error-first callback based code and opt in to Promises. Here are a few links to understand them and start using them efficiently.
110+
111+
- [Promises vs callbacks](promises.md)
112+
- [Manage your async control flow](https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20%26%20beyond/ch4.md#chapter-4-async-flow-control)
113+
114+
### Async/Await
115+
Async/await is part of the ES2017 release. As of Node 7.6, it's available natively without the `--harmony` flag.
116+
... to be continued.

promises.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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

Comments
 (0)