You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: control-flow/index.md
+52-54Lines changed: 52 additions & 54 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,7 +5,7 @@ author: donovan
5
5
date: Last Modified
6
6
---
7
7
8
-
As we create programs we write sequences of events. Usually, we expect them execute in order but this is not always the way it occurs. Even though JavaScript ios [single-threaded](https://medium.com/better-programming/is-node-js-really-single-threaded-7ea59bcc8d64'Is Node.js Really Single-Threaded?'), functions that call APIs, setTimeouts and other structures can result in our code running in an unexpected order.
8
+
JavaScript programs are made up of series of instructions. When our programs run, they run in sequence from the top down, one line at a time. Most lines of are _synchronous_, meaning they run in order. However not all do. Sometimes _asynchronous_ code can cause our code to execute in an unexpected order.
9
9
10
10
Consider the following code.
11
11
@@ -21,6 +21,8 @@ In the above code our compiler goes through each line executing each instruction
21
21
22
22
This is not what we want. We'll have to go wash our hands again. In production code, even worse things can happen.
23
23
24
+
Node generally runs in one single thread and any _blocking_ code will be run in sequence, with _non-blocking_ code having the potential to run _asynchronously_.
25
+
24
26
Thankfully Node offers 3 approaches we can use to control the order in which our code executes. _Callbacks_, _promises_ and _async/await_. Let's take a look at each.
25
27
26
28
## Callbacks
@@ -34,28 +36,28 @@ To simulate this I've created a `randomDelayedResponse` function that will retur
34
36
```
35
37
function randomDelayedResponse(text) {
36
38
// Using a timeout here for the sake of simulating an external request
Notice that the final line above returns `undefined`.
50
+
Notice that the final line above returns `undefined`. This is because the `randomDelayedResponse` line is _non_blocking_ and hasn't returned anything to `output` before we then try to apply `console.log` to it.
49
51
50
52
We need to wait until the response is ready. One way is to pass our `console.log` in to `randomDelayedResponse` as a function.
51
53
52
54
```
53
55
function randomDelayedResponse(text, callback) {
54
56
// Using a timeout here for the sake of simulating an external request
We can run the above code multiple times, and the result is unpredictable. It is _asynchronous_, in that the results do not arrive in order. To create a predictable, _synchronous_ flow using callbacks we'd need to nest them.
83
+
Here we pass in the function `console.log` as the second argument which is then run as `callback(text)` to log the output.
84
+
85
+
While the code does run in order and run the `randomDelayedResponse` function with the right sequence of inputs, the random delays mean they won't be logged in order. We can run the above code multiple times and the result is never predictable. Each call to the function is _asynchronous_, in that the results do not arrive in order. To create a predictable, _synchronous_ flow using callbacks we'd need to nest them.
88
86
89
87
```
90
88
function randomDelayedResponse(text, callback) {
91
89
// Using a timeout here for the sake of simulating an external request
@@ -109,7 +107,9 @@ randomDelayedResponse(1, text => {
109
107
}); // outputs "1 2 3 4"
110
108
```
111
109
112
-
Structuring these callbacks works. However this approach can create code becomes difficult to understand and maintain. Let's look at another way.
110
+
Structuring these callbacks outputs the numbers in the correct order. However when we use `callbacks` the code can become difficult to understand and maintain. This might be suitable in simple cases. Though if we find ourselves nesting multiple levels deep we should look for other ways to control the flow.
111
+
112
+
Let's look at one such alternative.
113
113
114
114
## Promises
115
115
@@ -133,10 +133,13 @@ We can rewrite our runner example using promises like so:
resolve(text); // Replacing the callback with "resolve"
139
-
}, timeOut * 10);
138
+
if (text) {
139
+
resolve(text); // Replacing the callback with "resolve"
140
+
}
141
+
reject('No text provided!'); // Reject when there is an error
142
+
}, timeOut);
140
143
});
141
144
}
142
145
@@ -157,32 +160,23 @@ randomDelayedResponse(1)
157
160
console.log(res);
158
161
})
159
162
.catch(error => {
160
-
// handle error
163
+
console.log(error);
161
164
})
162
165
// outputs "1 2 3 4"
163
166
```
164
167
165
168
Running the above code should output the correct order, albeit with random delays between each number.
166
169
167
-
We aren't handling errors in this example, but if our function was to encounter an error we would `reject('some error')`and this would then be picked up by the `catch`.
170
+
In the above example we shouldn't see any errors - but if you want to adjust one of the `andomDelayedResponse()`calls to pass in no data, it should `reject` and the `catch` block will log the error.
168
171
169
-
You can learn more about `promises` here (LINK)
170
-
171
-
Promises remove a lot of nesting and give us easier to read code. However there is a third way to control the execution order of our code.
172
+
Promises remove the nesting and give us easier to read code. Let's look at a third way that builds on this.
172
173
173
174
## Async / Await
174
175
175
-
The third approach is built on top of the existing _promises_ approach but makes it easier to reason with. With `async` and `await` we can write code that feels a lot more like the simple top-down code, by telling it to wait when we need it to. Let's rewrite our _who will win?_ example from above.
176
+
The third approach is built on top of the existing _promises_ approach and results in even simpler code. With `async` and `await` we can write code that feels a lot more like our usual top-down code. It works by telling our commands to wait when we need them to. Let's rewrite our _who will win?_ example from above.
async function runTheRace() { // put `async` before the function call
188
182
@@ -200,27 +194,31 @@ async function runTheRace() { // put `async` before the function call
200
194
runTheRace();
201
195
```
202
196
203
-
In the above we have replaced the series of `.then()` calls with an `async` function. It's still making use of a _promise_ but when we want to wait for the promise we simply place `await` before the asynchronous function. This way, the code execution order is preserved.
197
+
In the above we have replaced the series of `.then()` calls with an `async` function. It's still making use of a _promise_ but when we want to wait for the promise we simply place `await` before the asynchronous function. The code execution order is preserved.
204
198
205
199
You may be wondering how we handle errors here. One approach is to use the `try/catch` method. We'll be covering that and more in the [error handling section]({{ "/error-handling/" | url }}).
206
200
201
+
## Which is preferred?
202
+
203
+
Generally you should try to aim for the `async/await` approach when possible. It helps promote clean code. None of the approaches are to be avoided entirely, but in general always aim for the approach that ensures the code is easy to read and understand.
204
+
207
205
## Exercise
208
206
209
207
Given the following code, how would you change it so that the results always outputs `hello world`? Can you make it work using all 3 of the above approaches?
0 commit comments