├── .gitignore ├── 01_callback_hello_world.js ├── 02_callback_hell.js ├── 03_callbacks_named.js ├── 04_promises_hello_world.js ├── 05_promises_complicated.js ├── 06_coroutines_example.js ├── 07_async_await_example.js ├── 08_rx_hello_world.js ├── 09_rx_complicated.js ├── 10_csp_complicated.js ├── README.md ├── exploring_async.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directory 31 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 32 | node_modules 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | -------------------------------------------------------------------------------- /01_callback_hello_world.js: -------------------------------------------------------------------------------- 1 | setTimeout(() => console.log("Hello world!"), 1000); 2 | -------------------------------------------------------------------------------- /02_callback_hell.js: -------------------------------------------------------------------------------- 1 | var aBootTime = 1000, 2 | bBootTime = 1000, 3 | queueCallback = null, 4 | serverHandler = null; 5 | 6 | console.log("A: Booting up system...") 7 | setTimeout(() => { 8 | console.log("A: Checking network connection"); 9 | setTimeout(() => { 10 | console.log("A: Request complex computation"); 11 | 12 | sendRequest(value => { 13 | console.log("A: Computation returned " + value); 14 | }); 15 | }, 500); 16 | }, aBootTime); 17 | 18 | console.log("B: Booting up system...") 19 | setTimeout(() => { 20 | console.log("B: Server up and running"); 21 | serverHandler = (callback) => { 22 | console.log("B: Starting heavy computation"); 23 | setTimeout(() => callback(42), 2000) 24 | } 25 | if (queueCallback) { 26 | serverHandler(queueCallback); 27 | queueCallback = null; 28 | } 29 | }, bBootTime); 30 | 31 | function sendRequest(callback) { 32 | if(serverHandler) { 33 | serverHandler(callback); 34 | } else { 35 | queueCallback = callback; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /03_callbacks_named.js: -------------------------------------------------------------------------------- 1 | var aBootTime = 1000, 2 | bBootTime = 1000, 3 | queueCallback = null, 4 | serverHandler = null; 5 | 6 | serverA(); 7 | serverB(); 8 | 9 | function serverA() { 10 | console.log("A: Booting up system..."); 11 | setTimeout(checkNetwork, aBootTime); 12 | 13 | function checkNetwork() { 14 | console.log("A: Checking network connection"); 15 | setTimeout(sendRequest, 500); 16 | } 17 | 18 | function sendRequest() { 19 | console.log("A: Request complex computation"); 20 | sendNetworkRequest(callback); 21 | } 22 | 23 | function callback(value) { 24 | console.log("A: Computation returned " + value); 25 | } 26 | } 27 | 28 | function serverB() { 29 | console.log("B: Booting up system...") 30 | setTimeout(listenRequests, bBootTime); 31 | 32 | function listenRequests() { 33 | console.log("B: Server up and running"); 34 | serverHandler = handler; 35 | 36 | if (queueCallback) { 37 | serverHandler(queueCallback); 38 | queueCallback = null; 39 | } 40 | } 41 | 42 | function handler(callback) { 43 | console.log("B: Starting heavy computation"); 44 | setTimeout(() => callback(42), 2000) 45 | } 46 | } 47 | 48 | function sendNetworkRequest(callback) { 49 | if(serverHandler) { 50 | serverHandler(callback); 51 | } else { 52 | queueCallback = callback; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /04_promises_hello_world.js: -------------------------------------------------------------------------------- 1 | new Promise(resolve => setTimeout(() => resolve("Hello World!"), 1000)) 2 | .then(value => { 3 | console.log("Value!"); 4 | return new Promise(resolve => setTimeout(() => resolve("I'll be back"), 1000)); 5 | }) 6 | .then(value => console.log(value)); 7 | -------------------------------------------------------------------------------- /05_promises_complicated.js: -------------------------------------------------------------------------------- 1 | var Promise = require("bluebird"), 2 | aBootTime = 1000, 3 | bBootTime = 1000, 4 | promiseB; 5 | 6 | serverA(); 7 | promiseB = serverB(); 8 | 9 | function serverA() { 10 | console.log("A: Booting up system..."); 11 | return Promise.delay(aBootTime) 12 | .then(checkNetwork) 13 | .delay(500) 14 | .then(sendRequest); 15 | 16 | setTimeout(checkNetwork, aBootTime); 17 | 18 | function checkNetwork() { 19 | console.log("A: Checking network connection"); 20 | } 21 | 22 | function sendRequest() { 23 | console.log("A: Request complex computation"); 24 | sendNetworkRequest(callback); 25 | } 26 | 27 | function callback(value) { 28 | console.log("A: Computation returned " + value); 29 | } 30 | } 31 | 32 | function serverB() { 33 | console.log("B: Booting up system...") 34 | 35 | return Promise.delay(bBootTime).then(listenRequests); 36 | 37 | function listenRequests() { 38 | console.log("B: Server up and running"); 39 | return serverHandler; 40 | } 41 | 42 | function serverHandler(callback) { 43 | console.log("B: Starting heavy computation"); 44 | Promise.delay(2000).then(answerRequest); 45 | 46 | function answerRequest() { 47 | callback(42); 48 | } 49 | } 50 | } 51 | 52 | function sendNetworkRequest(callback) { 53 | promiseB.then(serverHandler => serverHandler(callback)); 54 | } 55 | -------------------------------------------------------------------------------- /06_coroutines_example.js: -------------------------------------------------------------------------------- 1 | var Promise = require("bluebird"), 2 | delay = Promise.delay, 3 | promisify = Promise.promisify, 4 | coroutine = Promise.coroutine, 5 | aBootTime = 1000, 6 | bBootTime = 1000, 7 | promiseB; 8 | 9 | coroutine(serverA)(); 10 | promiseB = coroutine(serverB)(); 11 | 12 | function* serverA() { 13 | console.log("A: Booting up system..."); 14 | yield delay(aBootTime); 15 | console.log("A: Checking network connection"); 16 | yield delay(500); 17 | console.log("A: Request complex computation"); 18 | var serverHandler = yield promiseB; 19 | var value = yield serverHandler(); 20 | console.log("A: Computation returned " + value); 21 | } 22 | 23 | function* serverB() { 24 | console.log("B: Booting up system...") 25 | yield delay(bBootTime); 26 | console.log("B: Server up and running"); 27 | return promisify(coroutine(serverHandler)); 28 | 29 | function* serverHandler(callback) { 30 | console.log("B: Starting heavy computation"); 31 | yield delay(2000); 32 | callback(null, 42); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /07_async_await_example.js: -------------------------------------------------------------------------------- 1 | // This uses experimental syntax. 2 | // To run it, use `regenerator -r 07_async_await_example.js | node` 3 | 4 | var Promise = require("bluebird"), 5 | delay = Promise.delay, 6 | promisify = Promise.promisify, 7 | coroutine = Promise.coroutine, 8 | aBootTime = 1000, 9 | bBootTime = 1000, 10 | promiseB; 11 | 12 | serverA(); 13 | promiseB = serverB(); 14 | 15 | async function serverA() { 16 | console.log("A: Booting up system..."); 17 | await delay(aBootTime); 18 | console.log("A: Checking network connection"); 19 | await delay(500); 20 | console.log("A: Request complex computation"); 21 | var serverHandler = await promiseB; 22 | var value = await serverHandler(); 23 | console.log("A: Computation returned " + value); 24 | } 25 | 26 | async function serverB() { 27 | console.log("B: Booting up system...") 28 | await delay(bBootTime); 29 | console.log("B: Server up and running"); 30 | return promisify(serverHandler); 31 | 32 | async function serverHandler(callback) { 33 | console.log("B: Starting heavy computation"); 34 | await delay(2000); 35 | callback(null, 42); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /08_rx_hello_world.js: -------------------------------------------------------------------------------- 1 | var Rx = require("rx"); 2 | 3 | Rx.Observable 4 | .interval(500) 5 | .map(x => x + 1) 6 | .takeWhile(x => x <= 3) 7 | .concat(Rx.Observable.of("World")) 8 | .subscribe(x => console.log("Hello " + x + "!")); 9 | -------------------------------------------------------------------------------- /09_rx_complicated.js: -------------------------------------------------------------------------------- 1 | var Promise = require("bluebird"), 2 | Rx = require("rx"), 3 | aBootTime = 1000, 4 | bBootTime = 1000, 5 | observableB; 6 | 7 | serverA(); 8 | observableB = serverB(); 9 | 10 | function serverA() { 11 | console.log("A: Booting up system..."); 12 | return Rx.Observable.timer(aBootTime) 13 | .do(checkNetwork) 14 | .delay(500) 15 | .flatMap(sendRequest) 16 | .subscribe(observer); 17 | 18 | setTimeout(checkNetwork, aBootTime); 19 | 20 | function checkNetwork() { 21 | console.log("A: Checking network connection"); 22 | } 23 | 24 | function sendRequest() { 25 | console.log("A: Request complex computation"); 26 | return Rx.Observable.fromCallback(sendNetworkRequest)(); 27 | } 28 | 29 | function observer(value) { 30 | console.log("A: Computation returned " + value); 31 | } 32 | } 33 | 34 | function serverB() { 35 | console.log("B: Booting up system...") 36 | var subject = new Rx.AsyncSubject(); 37 | Rx.Observable.timer(bBootTime).map(listenRequests).subscribe(subject); 38 | return subject; 39 | 40 | function listenRequests() { 41 | console.log("B: Server up and running"); 42 | return serverHandler; 43 | } 44 | 45 | function serverHandler(callback) { 46 | console.log("B: Starting heavy computation"); 47 | Rx.Observable.timer(2000).subscribe(answerRequest); 48 | 49 | function answerRequest() { 50 | callback(42); 51 | } 52 | } 53 | } 54 | 55 | function sendNetworkRequest(callback) { 56 | observableB.subscribe(serverHandler => serverHandler(callback)); 57 | } 58 | -------------------------------------------------------------------------------- /10_csp_complicated.js: -------------------------------------------------------------------------------- 1 | var aBootTime = 1000, 2 | bBootTime = 1000, 3 | csp = require("js-csp"), 4 | timeout = csp.timeout, 5 | network = csp.chan(); 6 | 7 | csp.go(serverA); 8 | csp.go(serverB); 9 | 10 | function* serverA() { 11 | console.log("A: Booting up system..."); 12 | yield timeout(aBootTime); 13 | console.log("A: Checking network connection"); 14 | yield timeout(500); 15 | console.log("A: Request complex computation"); 16 | yield csp.put(network, "request"); 17 | var value = yield csp.take(network); 18 | console.log("A: Computation returned " + value); 19 | network.close(); 20 | } 21 | 22 | function* serverB() { 23 | console.log("B: Booting up system...") 24 | yield timeout(bBootTime); 25 | console.log("B: Server up and running"); 26 | while(true) { 27 | var request = yield csp.take(network); 28 | if(request === csp.CLOSED) { 29 | break; 30 | } 31 | console.log("B: Starting heavy computation"); 32 | yield timeout(2000); 33 | yield csp.put(network, 42); 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # exploring-async 2 | This is the repository for an [essay](exploring_async.md) exploring different async techniques in JavaScript. 3 | 4 | 5 | -------------------------------------------------------------------------------- /exploring_async.md: -------------------------------------------------------------------------------- 1 | # Exploring Async Techniques in JavaScript 2 | 3 | During the past few months, I've been exploring a few different techniques that 4 | can be used to write asynchronous programs. I'd like to share my experience via this essay. 5 | 6 | Even though we are using JavaScript for this, many of these techniques can be used 7 | in other languages with equivalent manners. 8 | 9 | ## Callbacks 10 | 11 | In the JavaScript world, this is the simplest form of asynchronous programming 12 | and is used by almost all of the APIs in the language. It consists of passing 13 | a callback function as an argument in a function call, so the callback function 14 | is called when the desired behavior is supposed to happen. 15 | 16 | ```js 17 | setTimeout(() => console.log("Hello world!"), 1000); 18 | // Hello world! 19 | ``` 20 | 21 | The problem here is that it can get messy really fast when you are 22 | trying to make more complex programs, which leads to the frequent problem 23 | callback hell. 24 | 25 | ```js 26 | var aBootTime = 1000, 27 | bBootTime = 1000, 28 | queueCallback = null, 29 | serverHandler = null; 30 | 31 | console.log("A: Booting up system...") 32 | setTimeout(() => { 33 | console.log("A: Checking network connection"); 34 | setTimeout(() => { 35 | console.log("A: Request complex computation"); 36 | 37 | sendRequest(value => { 38 | console.log("A: Computation returned " + value); 39 | }); 40 | }, 500); 41 | }, aBootTime); 42 | 43 | console.log("B: Booting up system...") 44 | setTimeout(() => { 45 | console.log("B: Server up and running"); 46 | serverHandler = (callback) => { 47 | console.log("B: Starting heavy computation"); 48 | setTimeout(() => callback(42), 2000) 49 | } 50 | if (queueCallback) { 51 | serverHandler(queueCallback); 52 | queueCallback = null; 53 | } 54 | }, bBootTime); 55 | 56 | function sendRequest(callback) { 57 | if(serverHandler) { 58 | serverHandler(callback); 59 | } else { 60 | queueCallback = callback; 61 | } 62 | } 63 | // A: Booting up system... 64 | // B: Booting up system... 65 | // A: Checking network connection 66 | // B: Server up and running 67 | // A: Request complex computation 68 | // B: Starting heavy computation 69 | // A: Computation returned 42 70 | ``` 71 | 72 | In the snippet above, we have servers A and B. As soon as server A boots up and 73 | checks its network connection, it sends a computation request to server B. Once 74 | the computation is done, server B responds to server A. Finally, server A prints 75 | the response to the screen. Because of the necessity for two different parts 76 | of the code to communicate with each other, we have global variables to share 77 | state among the two different servers. That, in turn, make it really complicated 78 | to follow up what's going on in the code, hence the callback hell. 79 | 80 | One possible way we can improve the code above is to improve the code is 81 | to name the callbacks and reduce the indentation level of the named functions. 82 | 83 | ```js 84 | var aBootTime = 1000, 85 | bBootTime = 1000, 86 | queueCallback = null, 87 | serverHandler = null; 88 | 89 | serverA(); 90 | serverB(); 91 | 92 | function serverA() { 93 | console.log("A: Booting up system..."); 94 | setTimeout(checkNetwork, aBootTime); 95 | 96 | function checkNetwork() { 97 | console.log("A: Checking network connection"); 98 | setTimeout(sendRequest, 500); 99 | } 100 | 101 | function sendRequest() { 102 | console.log("A: Request complex computation"); 103 | sendNetworkRequest(callback); 104 | } 105 | 106 | function callback(value) { 107 | console.log("A: Computation returned " + value); 108 | } 109 | } 110 | 111 | function serverB() { 112 | console.log("B: Booting up system...") 113 | setTimeout(listenRequests, bBootTime); 114 | 115 | function listenRequests() { 116 | console.log("B: Server up and running"); 117 | serverHandler = handler; 118 | 119 | if (queueCallback) { 120 | serverHandler(queueCallback); 121 | queueCallback = null; 122 | } 123 | } 124 | 125 | function handler(callback) { 126 | console.log("B: Starting heavy computation"); 127 | setTimeout(() => callback(42), 2000) 128 | } 129 | } 130 | 131 | function sendNetworkRequest(callback) { 132 | if(serverHandler) { 133 | serverHandler(callback); 134 | } else { 135 | queueCallback = callback; 136 | } 137 | } 138 | // A: Booting up system... 139 | // B: Booting up system... 140 | // A: Checking network connection 141 | // B: Server up and running 142 | // A: Request complex computation 143 | // B: Starting heavy computation 144 | // A: Computation returned 42 145 | ``` 146 | 147 | Even though it's now easier to follow, there's a lot of room for improvement. 148 | For that, let's go ahead to the next technique in our agenda. 149 | 150 | ## Promises 151 | 152 | Promises are the solution preferred by the JavaScript community to avoid 153 | callback hell. It defines an API that handles asynchronous events elegantly. 154 | When you have a promise, you can pass a call `.then` passing in a callback 155 | function for when the Promise is done with our computation. 156 | 157 | The key point here is that, in every `.then` function call, a new Promise is 158 | returned when the previous callback is done. That allows us to chain Promises 159 | together, so we can compose really complex behaviors. 160 | 161 | ```js 162 | new Promise(resolve => setTimeout(() => resolve("Hello World!"), 1000)) 163 | .then(value => { 164 | console.log("Value!"); 165 | return new Promise(resolve => setTimeout(() => resolve("I'll be back"), 1000)); 166 | }) 167 | .then(value => console.log(value)); 168 | // Value! 169 | // I'll be back 170 | ``` 171 | 172 | With this new trick up our sleeves, let's try to rewrite our previous server 173 | example, but making use of the [Bluebird][1] library, which is an efficient 174 | implementation of Promises that has several support methods which we are going 175 | to use in these examples. 176 | 177 | ```js 178 | var Promise = require("bluebird"), 179 | aBootTime = 1000, 180 | bBootTime = 1000, 181 | promiseB; 182 | 183 | serverA(); 184 | promiseB = serverB(); 185 | 186 | function serverA() { 187 | console.log("A: Booting up system..."); 188 | return Promise.delay(aBootTime) 189 | .then(checkNetwork) 190 | .delay(500) 191 | .then(sendRequest); 192 | 193 | setTimeout(checkNetwork, aBootTime); 194 | 195 | function checkNetwork() { 196 | console.log("A: Checking network connection"); 197 | } 198 | 199 | function sendRequest() { 200 | console.log("A: Request complex computation"); 201 | sendNetworkRequest(callback); 202 | } 203 | 204 | function callback(value) { 205 | console.log("A: Computation returned " + value); 206 | } 207 | } 208 | 209 | function serverB() { 210 | console.log("B: Booting up system...") 211 | 212 | return Promise.delay(bBootTime).then(listenRequests); 213 | 214 | function listenRequests() { 215 | console.log("B: Server up and running"); 216 | return serverHandler; 217 | } 218 | 219 | function serverHandler(callback) { 220 | console.log("B: Starting heavy computation"); 221 | Promise.delay(2000).then(answerRequest); 222 | 223 | function answerRequest() { 224 | callback(42); 225 | } 226 | } 227 | } 228 | 229 | function sendNetworkRequest(callback) { 230 | promiseB.then(serverHandler => serverHandler(callback)); 231 | } 232 | // A: Booting up system... 233 | // B: Booting up system... 234 | // A: Checking network connection 235 | // B: Server up and running 236 | // A: Request complex computation 237 | // B: Starting heavy computation 238 | // A: Computation returned 42 239 | ``` 240 | 241 | Notice that, in the snippet above, we've removed the shared state between 242 | the servers. Previously, it was used to keep track of which server was up and 243 | running. Because Promises can simple callback once it's done with its 244 | computation, the need for shared state went away. 245 | 246 | ## Generator Coroutines 247 | 248 | The generator coroutines technique makes a smart use of the new generator feature 249 | of the ES6 specification. Generators make it possible to suspend and resume 250 | function execution. [Bluebird][1] uses it to provide a convenient way to 251 | await the return of promises, almost as if it was a synchronous function call. 252 | Our previous example become a lot simpler once we make use of the coroutine 253 | feature. 254 | 255 | ```js 256 | var Promise = require("bluebird"), 257 | delay = Promise.delay, 258 | promisify = Promise.promisify, 259 | coroutine = Promise.coroutine, 260 | aBootTime = 1000, 261 | bBootTime = 1000, 262 | promiseB; 263 | 264 | coroutine(serverA)(); 265 | promiseB = coroutine(serverB)(); 266 | 267 | function* serverA() { 268 | console.log("A: Booting up system..."); 269 | yield delay(aBootTime); 270 | console.log("A: Checking network connection"); 271 | yield delay(500); 272 | console.log("A: Request complex computation"); 273 | var serverHandler = yield promiseB; 274 | var value = yield serverHandler(); 275 | console.log("A: Computation returned " + value); 276 | } 277 | 278 | function* serverB() { 279 | console.log("B: Booting up system...") 280 | yield delay(bBootTime); 281 | console.log("B: Server up and running"); 282 | return promisify(coroutine(serverHandler)); 283 | 284 | function* serverHandler(callback) { 285 | console.log("B: Starting heavy computation"); 286 | yield delay(2000); 287 | callback(null, 42); 288 | } 289 | } 290 | // A: Booting up system... 291 | // B: Booting up system... 292 | // A: Checking network connection 293 | // B: Server up and running 294 | // A: Request complex computation 295 | // B: Starting heavy computation 296 | // A: Computation returned 42 297 | ``` 298 | 299 | In the snippet above, the `coroutine` function takes in a generator `function*`, 300 | which makes it possible to `yield` promises, get their response, and resume 301 | the function from where it left off. Notice that it manages to describe our 302 | problem with a greater simplicity, almost as if we were writing a synchronous 303 | program. 304 | 305 | ## Async & Await 306 | 307 | Inspired by C#, the Async & Await is currently a proposal for the ES2016 308 | specification, and it's syntax is experimental. It might change upon final 309 | release. Nevertheless, it consists of the same idea of the Generator Coroutines method. 310 | Inside an function marked with the `async` keyword, you're able to `await` the 311 | result of Promises, such that you have a very similar structure to the 312 | previous technique. 313 | 314 | ```js 315 | var Promise = require("bluebird"), 316 | delay = Promise.delay, 317 | promisify = Promise.promisify, 318 | coroutine = Promise.coroutine, 319 | aBootTime = 1000, 320 | bBootTime = 1000, 321 | promiseB; 322 | 323 | serverA(); 324 | promiseB = serverB(); 325 | 326 | async function serverA() { 327 | console.log("A: Booting up system..."); 328 | await delay(aBootTime); 329 | console.log("A: Checking network connection"); 330 | await delay(500); 331 | console.log("A: Request complex computation"); 332 | var serverHandler = await promiseB; 333 | var value = await serverHandler(); 334 | console.log("A: Computation returned " + value); 335 | } 336 | 337 | async function serverB() { 338 | console.log("B: Booting up system...") 339 | await delay(bBootTime); 340 | console.log("B: Server up and running"); 341 | return promisify(serverHandler); 342 | 343 | async function serverHandler(callback) { 344 | console.log("B: Starting heavy computation"); 345 | await delay(2000); 346 | callback(null, 42); 347 | } 348 | } 349 | // A: Booting up system... 350 | // B: Booting up system... 351 | // A: Checking network connection 352 | // B: Server up and running 353 | // A: Request complex computation 354 | // B: Starting heavy computation 355 | // A: Computation returned 42 356 | ``` 357 | 358 | When you compare the snippet above with the previous example, you notice the 359 | syntax is a little cleaner, which is to be expected as that's the use case for 360 | which `async` and `await` were designed, whereas Generators were originally 361 | designed to generate enumerable values in a lazy manner. However, because this 362 | feature is currently experimental, you are better off using the Generator Coroutines 363 | technique in your projects, as you only require generators, which are already 364 | very well supported in the JavaScript world. 365 | 366 | ## Reactive Extensions 367 | 368 | Also influenced by the C# community, Reactive Extensions (Rx), made available in 369 | JavaScript through [Rx.js][2], it makes it possible to use enumerable higher 370 | order functions (such as map, filter and reduce) over streams of asynchronous 371 | events. It makes it a more appropriate tool for when you have to manipulate 372 | several asynchronous events at once. 373 | 374 | ```js 375 | var Rx = require("rx"); 376 | 377 | Rx.Observable 378 | .interval(500) 379 | .map(x => x + 1) 380 | .takeWhile(x => x <= 3) 381 | .concat(Rx.Observable.of("World")) 382 | .subscribe(x => console.log("Hello " + x + "!")); 383 | // Hello 1! 384 | // Hello 2! 385 | // Hello 3! 386 | // Hello World! 387 | ``` 388 | 389 | In the snippet above, we make use of `map`, `takeWhile` and `concat` just as 390 | if we were working with an array. The difference here is that the events 391 | happen over time. 392 | 393 | Now, we are going to try to make use of Reactive Extensions to implement our 394 | server communication example. 395 | 396 | ```js 397 | var Promise = require("bluebird"), 398 | Rx = require("rx"), 399 | aBootTime = 1000, 400 | bBootTime = 1000, 401 | observableB; 402 | 403 | serverA(); 404 | observableB = serverB(); 405 | 406 | function serverA() { 407 | console.log("A: Booting up system..."); 408 | return Rx.Observable.timer(aBootTime) 409 | .do(checkNetwork) 410 | .delay(500) 411 | .flatMap(sendRequest) 412 | .subscribe(observer); 413 | 414 | setTimeout(checkNetwork, aBootTime); 415 | 416 | function checkNetwork() { 417 | console.log("A: Checking network connection"); 418 | } 419 | 420 | function sendRequest() { 421 | console.log("A: Request complex computation"); 422 | return Rx.Observable.fromCallback(sendNetworkRequest)(); 423 | } 424 | 425 | function observer(value) { 426 | console.log("A: Computation returned " + value); 427 | } 428 | } 429 | 430 | function serverB() { 431 | console.log("B: Booting up system...") 432 | var subject = new Rx.AsyncSubject(); 433 | Rx.Observable.timer(bBootTime).map(listenRequests).subscribe(subject); 434 | return subject; 435 | 436 | function listenRequests() { 437 | console.log("B: Server up and running"); 438 | return serverHandler; 439 | } 440 | 441 | function serverHandler(callback) { 442 | console.log("B: Starting heavy computation"); 443 | Rx.Observable.timer(2000).subscribe(answerRequest); 444 | 445 | function answerRequest() { 446 | callback(42); 447 | } 448 | } 449 | } 450 | 451 | function sendNetworkRequest(callback) { 452 | observableB.subscribe(serverHandler => serverHandler(callback)); 453 | } 454 | // A: Booting up system... 455 | // B: Booting up system... 456 | // A: Checking network connection 457 | // B: Server up and running 458 | // A: Request complex computation 459 | // B: Starting heavy computation 460 | // A: Computation returned 42 461 | ``` 462 | 463 | In the snippet above, we make use of an AsyncSubject in order for the Observable 464 | result to be readily available when we subscribe to it. If you want to learn 465 | more about it, please read about [Hot and Cold observables][3]. 466 | 467 | From the snippet above, we notice the implementation is a lot more complicated 468 | than the Generator Coroutine version, and slightly more complicated than the 469 | Promises version. It happens because this isn't the most appropriate technique 470 | to solve our problem, as we are dealing with with several types of non repeating 471 | asynchronous events, as opposed to fewer types of repeating events. 472 | 473 | So, it might be worth it to learn Reactive Extensions because there will be 474 | situations in which your problem will be more easily solved by Promises, 475 | whereas others will have simpler solutions with Reactive Extensions. Another 476 | advantage of learning Reactive Extensions is that it has a relatively uniform 477 | API implementations across [many different languages][4], such as [Ruby][5], [Lua][6]. 478 | 479 | ## Communicating Sequential Processes 480 | 481 | Made popular by Go, this technique, CSP for short, has the concept of processes 482 | independent of each other which communicate solely by channels. The idea here is that, 483 | if a process tries to write to a channel and there is no other process reading from it, 484 | it blocks. If a process tries to read from a channel, but there is no other process to write 485 | to it, it blocks. 486 | 487 | The [js-csp][7] library manages to implement it in JavaScript by also making a smart use of 488 | generators. Let's see how our servers example looks like with it. 489 | 490 | ```js 491 | var aBootTime = 1000, 492 | bBootTime = 1000, 493 | csp = require("js-csp"), 494 | timeout = csp.timeout, 495 | network = csp.chan(); 496 | 497 | csp.go(serverA); 498 | csp.go(serverB); 499 | 500 | function* serverA() { 501 | console.log("A: Booting up system..."); 502 | yield timeout(aBootTime); 503 | console.log("A: Checking network connection"); 504 | yield timeout(500); 505 | console.log("A: Request complex computation"); 506 | yield csp.put(network, "request"); 507 | var value = yield csp.take(network); 508 | console.log("A: Computation returned " + value); 509 | network.close(); 510 | } 511 | 512 | function* serverB() { 513 | console.log("B: Booting up system...") 514 | yield timeout(bBootTime); 515 | console.log("B: Server up and running"); 516 | while(true) { 517 | var request = yield csp.take(network); 518 | if(request === csp.CLOSED) { 519 | break; 520 | } 521 | console.log("B: Starting heavy computation"); 522 | yield timeout(2000); 523 | yield csp.put(network, 42); 524 | }; 525 | } 526 | // A: Booting up system... 527 | // B: Booting up system... 528 | // A: Checking network connection 529 | // B: Server up and running 530 | // A: Request complex computation 531 | // B: Starting heavy computation 532 | // A: Computation returned 42 533 | ``` 534 | 535 | In the snippet above, each time we have a `yield csp.put`, we write to the channel. 536 | Each time we have a `yield csp.take`, we block execution until there is a value to 537 | read from. Differently than the Generator Coroutines method, here we have a direct 538 | communication channel between the two servers. CSP makes the code looks like as if 539 | we are writing two completely different sequential programs. 540 | 541 | Another advantage to this technique is that, because a channel is also a sequence 542 | of asynchronous data, you can also make use of enumerable higher order functions 543 | (map, filter, reduce, etc), albeit fewer higher order functions when compared to 544 | Reactive Extensions. 545 | 546 | 547 | ## Summary 548 | 549 | In this essay, we explored several different asynchronous programming techniques 550 | which can be used in JavaScript. Some of them tend to be more functional, making 551 | heavier use of functions in order to abstract the code, whereas in others you 552 | you think more in an imperative manner. I guess a nice way to put it is in the table 553 | below. 554 | 555 |
556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 568 | 571 | 572 | 573 | 574 | 578 | 579 | 580 |
OneMultiple
Functional 565 | Callbacks
566 | Promises
567 |
569 | Reactive
Extensions 570 |
Imperative 575 | Generator Coroutines
576 | Async & Await
577 |
Communicating
Sequential
Processes
581 |
582 | 583 | Please take notice these techniques aren't mutually exclusive. 584 | 585 | In my use cases, I'd probably adopt the following strategies: 586 | 587 | * In simple function calls, I'm probably better off with callbacks 588 | 589 | * When I have to deal with many different asynchronous returns, maybe 590 | the way to go is to use Promises and tie them together with a Generator Coroutine 591 | 592 | * When I have sequences of events, be it a stream of positions of 593 | the cursor on the screen, that's probably more easily handled with Reactive Extensions. 594 | 595 | * In any situation I would have to deal with mutable state, I'd probably 596 | be better off with CSP. 597 | 598 | [1]: http://bluebirdjs.com/ 599 | [2]: https://github.com/Reactive-Extensions/RxJS 600 | [3]: http://www.introtorx.com/content/v1.0.10621.0/14_HotAndColdObservables.html 601 | [4]: http://reactivex.io/ 602 | [5]: https://github.com/ReactiveX/RxRuby 603 | [6]: https://github.com/bjornbytes/RxLua 604 | [7]: https://github.com/ubolonton/js-csp 605 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exploring-async", 3 | "version": "0.0.1", 4 | "description": "Code to explore the different async techniques in JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Thales Mello", 10 | "license": "ISC", 11 | "dependencies": { 12 | "babel": "^6.3.26", 13 | "bluebird": "^3.1.1", 14 | "js-csp": "^0.5.0", 15 | "rx": "^4.0.7" 16 | } 17 | } 18 | --------------------------------------------------------------------------------