Slide 1

Slide 1 text

scale your multicore JavaScript… elegantly. Async patterns to

Slide 2

Slide 2 text

I’m Jonathan Lee Martin. @nybblr

Slide 3

Slide 3 text

I teach developers to craft exceptional software.

Slide 4

Slide 4 text

I’ve worked with over 300 developers — from career switchers to senior developers at Fortune 100 companies — through their journey into software development.

Slide 5

Slide 5 text

Let’s do something together. jonathanleemartin.com Functional React 3 days Practical TDD 5 days Full Stack JavaScript 8 week Express in a Hurry 1 day Remote Collaboration 5 days Functional Programming in JS 3 days

Slide 6

Slide 6 text

I travel the world as a landscape photographer. !

Slide 7

Slide 7 text

A

Slide 8

Slide 8 text

" in !

Slide 9

Slide 9 text

" in ! to for Take a Make a ☎ on " a

Slide 10

Slide 10 text

to for Take a Make a ☎ on " a " in !

Slide 11

Slide 11 text

task3() task1() task2()

Slide 12

Slide 12 text

eatBreakfast() emails() googleHangout()

Slide 13

Slide 13 text

eatBreakfast() Time emails() googleHangout() Task #3 Task #2 Task #1

Slide 14

Slide 14 text

task3() Time task1() task2() Thread #3 Thread #2 Thread #1 eatBreakfast() emails() googleHangout() Task #3 Task #2 Task #1

Slide 15

Slide 15 text

task3() Time task1() task2() Core #3 Core #2 Core #1 eatBreakfast() emails() googleHangout() Task #3 Task #2 Task #1

Slide 16

Slide 16 text

Did I… Mishear you? Sigh, one less excuse to dismiss JavaScript.

Slide 17

Slide 17 text

JavaScript is highly concurrent in Node and the browser. fetch('videos.json') setTimeout(refresh, 5000) db.transaction(['videos']) .objectStore('videos') .get('lotr-1') 2 1 3 4

Slide 18

Slide 18 text

How does this magic work? Event Loop & Web APIs

Slide 19

Slide 19 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer fetch('videos.json') .then(parse) setTimeout(refresh, 5000) db.transaction(['videos']) .objectStore('videos') .get('lotr-1') .then(render) stack.empty? && !queue.empty? Source Code

Slide 20

Slide 20 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer fetch('videos.json') .then(parse) setTimeout(refresh, 5000) db.transaction(['videos']) .objectStore('videos') .get('lotr-1') .then(render) stack.empty? && !queue.empty? Source Code parse()

Slide 21

Slide 21 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer fetch('videos.json') .then(parse) setTimeout(refresh, 5000) db.transaction(['videos']) .objectStore('videos') .get('lotr-1') .then(render) stack.empty? && !queue.empty? Source Code parse()

Slide 22

Slide 22 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer setTimeout(refresh, 5000) db.transaction(['videos']) .objectStore('videos') .get('lotr-1') .then(render) stack.empty? && !queue.empty? Source Code parse() refresh()

Slide 23

Slide 23 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer setTimeout(refresh, 5000) db.transaction(['videos']) .objectStore('videos') .get('lotr-1') .then(render) stack.empty? && !queue.empty? Source Code parse() refresh()

Slide 24

Slide 24 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer db.transaction(['videos']) .objectStore('videos') .get('lotr-1') .then(render) stack.empty? && !queue.empty? Source Code parse() refresh() render()

Slide 25

Slide 25 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer stack.empty? && !queue.empty? Source Code parse() refresh() render()

Slide 26

Slide 26 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer stack.empty? && !queue.empty? Source Code parse() refresh() render()

Slide 27

Slide 27 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer stack.empty? && !queue.empty? Source Code parse() refresh() render()

Slide 28

Slide 28 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer stack.empty? && !queue.empty? Source Code refresh() render() parse()

Slide 29

Slide 29 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer stack.empty? && !queue.empty? Source Code render() parse() refresh()

Slide 30

Slide 30 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer stack.empty? && !queue.empty? Source Code parse() refresh()

Slide 31

Slide 31 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer stack.empty? && !queue.empty? Source Code refresh() parse()

Slide 32

Slide 32 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer stack.empty? && !queue.empty? Source Code refresh()

Slide 33

Slide 33 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer stack.empty? && !queue.empty? Source Code refresh()

Slide 34

Slide 34 text

Call Stack Web APIs Callback Queue Event Loop Fetch IndexedDB Timer stack.empty? && !queue.empty? Source Code

Slide 35

Slide 35 text

bit.ly/event-loop-help

Slide 36

Slide 36 text

Just use multiple processes! PM2, require('cluster')

Slide 37

Slide 37 text

Today, we’ll cover: 1. Declare concurrent dependencies with Async IIFEs 2. Manage concurrency with Functional Programming 3. Create your own threads with Web Workers

Slide 38

Slide 38 text

Async IIFEs 1. Declare concurrent dependencies with

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

1. Read File 2. Parse ID3 Metadata 3. Calculate Duration 4. Import Album 5. Import Song — bit.ly/id3-parser

Slide 41

Slide 41 text

// Read the file let buffer = await read(file); // Parse out the ID3 metadata let meta = await parser(file); let songMeta = mapSongMeta(meta); let albumMeta = mapAlbumMeta(meta); // Compute the duration let duration = await getDuration(buffer); // Import the album let albumId = await importAlbum(albumMeta); // Import the song let songId = await importSong({ ...songMeta, albumId, file, duration, meta }); return songId; 1. Read File 2. Parse ID3 Metadata 3. Calculate Duration 4. Import Album 5. Import Song

Slide 42

Slide 42 text

let [ buffer, meta ] = await Promise.all([ // Read the file read(file), // Parse out the ID3 metadata parser(file) ]); let songMeta = mapSongMeta(meta); let albumMeta = mapAlbumMeta(meta); let [ duration, albumId ] = await Promise.all([ // Compute the duration getDuration(buffer), // Import the album importAlbum(albumMeta) ]);

Slide 43

Slide 43 text

Async IIFEs • Async Immediately Invoked Function Expression (IIFE) (async () => { /* do things */ })();

Slide 44

Slide 44 text

Async IIFEs • Async Immediately Invoked Function Expression (IIFE) let task = (async () => { let thing = await otherTask; let result = await doThings(thing); return result; })();

Slide 45

Slide 45 text

let readTask = read(file); // Parse out the ID3 metadata let metaTask = (async () => { let meta = await parser(file); let songMeta = mapSongMeta(meta); let albumMeta = mapAlbumMeta(meta); return { meta, songMeta, albumMeta }; })(); // Import the album let albumImportTask = (async () => { let { albumMeta } = await metaTask; let albumId = await importAlbum(albumMeta); return albumId;

Slide 46

Slide 46 text

let readTask = read(file); // Parse out the ID3 metadata let metaTask = (async () => { let meta = await parser(file); let songMeta = mapSongMeta(meta); let albumMeta = mapAlbumMeta(meta); return { meta, songMeta, albumMeta }; })(); // Import the album let albumImportTask = (async () => { let { albumMeta } = await metaTask; let albumId = await importAlbum(albumMeta); return albumId; })(); // Compute the duration let durationTask = (async () => { let buffer = await readTask; let duration = await getDuration(buffer); return duration; })(); // Import the song let songImportTask = (async () => { let albumId = await albumImportTask; let { meta, songMeta } = await metaTask; let duration = await durationTask; let songId = await importSong({ ...songMeta, albumId, file, duration, meta }); return songId; })(); let songId = await songImportTask; return songId; metaTask readTask durationTask albumImportTask songImportTask return songId

Slide 47

Slide 47 text

let readTask = read(file); // Parse out the ID3 metadata let metaTask = (async () => { let meta = await parser(file); let songMeta = mapSongMeta(meta); let albumMeta = mapAlbumMeta(meta); return { meta, songMeta, albumMeta }; })(); // Import the album let albumImportTask = (async () => { let { albumMeta } = await metaTask; let albumId = await importAlbum(albumMeta); return albumId; })(); // Compute the duration let durationTask = (async () => { let buffer = await readTask; let duration = await getDuration(buffer); return duration; })(); // Import the song let songImportTask = (async () => { let albumId = await albumImportTask; let { meta, songMeta } = await metaTask; let duration = await durationTask; let songId = await importSong({ ...songMeta, albumId, file, duration, meta }); return songId; })(); let songId = await songImportTask; return songId; metaTask readTask durationTask albumImportTask songImportTask return songId (file) =>

Slide 48

Slide 48 text

bit.ly/async-iifes

Slide 49

Slide 49 text

Functional Programming 2. Manage concurrency with

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

Importing 01 Overture.mp3

Slide 52

Slide 52 text

Importing 02 The Grid.mp3

Slide 53

Slide 53 text

Importing 22 Finale.mp3

Slide 54

Slide 54 text

let semaphore = new Semaphore(4); await semaphore.acquire(); /* do things */ semaphore.release();

Slide 55

Slide 55 text

class Semaphore { constructor(max) { this.tasks = []; this.counter = max; this.dispatch = this.dispatch.bind(this); } dispatch() { if (this.counter > 0 && this.tasks.length > 0) { this.counter--; this.tasks.shift()(); } } release() { this.counter++; this.dispatch();

Slide 56

Slide 56 text

let semaphore = new Semaphore(4); await semaphore.acquire(); /* do things */ semaphore.release();

Slide 57

Slide 57 text

let semaphore = new Semaphore(4); await semaphore.acquire(); throw new Error('BOOM'); semaphore.release();

Slide 58

Slide 58 text

let Semaphore = (max) => { let tasks = []; let counter = max; let dispatch = () => { if (counter > 0 && tasks.length > 0) { counter--; tasks.shift()(); } }; let release = () => { counter++; dispatch(); }; let acquire = () =>

Slide 59

Slide 59 text

let semaphore = Semaphore(4); let result = await semaphore( async () => { console.log('Acquired!'); return await importMP3(file); } );

Slide 60

Slide 60 text

let importMP3 = async (data) => /* … */ let limitedImportMP3 = limit(2, importMP3);

Slide 61

Slide 61 text

let importMP3 = async (data) => /* … */ let limitedImportMP3 = limit(2, importMP3); limitedImportMP3(song1); // starts immediately limitedImportMP3(song2); // starts immediately limitedImportMP3(song3); // waits for song1 or song2 to finish

Slide 62

Slide 62 text

let importMP3 = async (data) => /* … */ let limitedImportMP3 = limit(2, importMP3); let limit = (max, fn) => { let semaphore = Semaphore(max); return (...args) => semaphore(() => fn(...args)); };

Slide 63

Slide 63 text

bit.ly/semaphorejs

Slide 64

Slide 64 text

Web Workers 3. Create your own threads with

Slide 65

Slide 65 text

Fetch setTimeout WebRTC IndexedDB Audio FileReader Bluetooth Crypto ServiceWorker WebSocket WebWorker

Slide 66

Slide 66 text

Audio IndexedDB setTimeout Fetch Thread Pool (x8) Main Thread fetch('videos.json') setTimeout(refresh, 5000) db.transaction(['videos']) .objectStore('videos') .get('lotr-1') ctx.decodeAudioData(buffer)

Slide 67

Slide 67 text

Audio IndexedDB setTimeout Fetch Thread Pool (x8) Main Thread importMP3(file) fetch('videos.json') setTimeout(refresh, 5000) db.transaction(['videos']) .objectStore('videos') .get('lotr-1') ctx.decodeAudioData(buffer) MySongImporter

Slide 68

Slide 68 text

let worker = new Worker('worker.js'); worker.postMessage({ all: ['the', 'data']}); worker.onmessage(({ data }) => { console.log(data); });

Slide 69

Slide 69 text

let importMP3 = Cluster('mp3-worker.js'); let song = await importMP3( songFile );

Slide 70

Slide 70 text

let maxWorkers = navigator.hardwareConcurrency || 4; let defaultHandler = async (worker, data) => { worker.postMessage(data); return await once('message'); }; let Cluster = ( path, handler = defaultHandler, max = maxWorkers ) => { let pool = []; let semaphore = Semaphore(max); let useWorker = async (fn) => {

Slide 71

Slide 71 text

import { done } from './rpc'; (async () => { while (true) { let { data } = await once(self, 'message'); let song = await importMP3(data); done(song); } })();

Slide 72

Slide 72 text

TL;DR: 1. JavaScript is highly concurrent 2. Thread-safety isn’t a thing 3. Functional programming keeps it elegant 4. Make your own async APIs with Web Workers

Slide 73

Slide 73 text

Always bet on JavaScript™

Slide 74

Slide 74 text

Let’s do something together. jonathanleemartin.com