Slide 1

Slide 1 text

An opinionated intro to Node.js Luciano Mammino @loige

Slide 2

Slide 2 text

👋 Hello, I am Luciano Cloud & Full stack software engineer nodejsdesignpatterns.com Let’s connect: 🌎 loige.co 🐦 @loige 🧳 lucianomammino

Slide 3

Slide 3 text

👇 Get the slides (and click around…) loige.link/devrupt-node - loige.link/devrupt-node-code @loige

Slide 4

Slide 4 text

Agenda Node.js intro Blocking vs async I/O Dealing with async I/O Some interesting patterns @loige

Slide 5

Slide 5 text

What is Node.js Node.js is an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside a web browser. @loige

Slide 6

Slide 6 text

Ryan Dahl Created and launched in 2009 by Ryan Dahl to have a way to run JavaScript code outside a browser. npm was launched in the same year. Big companies (Linkedin, Uber, Paypal, Walmart) adopted Node.js early on. @loige

Slide 7

Slide 7 text

Node.js vs JavaScript in the browser ● Node.js does not run in the browser ○ It doesn’t have a DOM ○ It doesn’t have browser specific APIs ● It is not as sandboxed as the browser ○ Node.js can access the filesystem ○ It can interact with the network layer (TCP/UDP) ○ ...and even have bindings to native libraries (N-API interface) @loige

Slide 8

Slide 8 text

Node.js + JavaScript: when? ● Building for the web ○ Websites, APIs, Servers, Single Page Applications, Bots, etc… ● Command-line applications and tools ● Mobile applications (React Native, Ionic, NativeScript, etc.) ● Desktop apps (Electron) ● Embedded & IoT (Espruino, Johnny-Five) @loige

Slide 9

Slide 9 text

Node.js + JavaScript: when not? ● You don’t like the async model ● You need fine-grained control on memory ● You have to rely heavily on CPU/GPU rather than I/O ● You prefer to release native (compiled) applications @loige

Slide 10

Slide 10 text

Async I/O In JavaScript and in Node.js, input/output operations (e.g. making an HTTP request) are non-blocking. @loige

Slide 11

Slide 11 text

OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("https://google.com") .build(); Response response = client.newCall(request).execute(); System.out.println(response.body().string()); System.out.println("Request completed"); A blocking HTTP request (Java) Output blocking... blocking... Request completed Code executed “In order” @loige

Slide 12

Slide 12 text

Is blocking I/O bad? It depends… If your application is I/O heavy, then you might have a problem... @loige

Slide 13

Slide 13 text

Let’s make 3 requests... Req 1 Req 2 Req 3 time @loige

Slide 14

Slide 14 text

You can always use threads... Req 1 Req 2 Req 3 time But threads are… Complicated Expensive A lot of idle time per thread! wait... wait... wait... @loige

Slide 15

Slide 15 text

client.get('https://google.com', (err, resp) => { console.log(resp.body) } ) console.log('Request completed (?)') 👍 With Node.js async I/O Output Request “in the background” Request completed (?) ⚠ Code executed “OUT of order” Not really completed! Non-blocking: execution continues Callback function 1 2 3 @loige

Slide 16

Slide 16 text

Mental model You “just” schedule async I/O and you will get notified when the operation is completed! ● Async I/O happens in the background asynchronously ● You don’t have to manage threads to get concurrency! @loige

Slide 17

Slide 17 text

Let’s do 3 requests with Async I/O “User” thread Event loop (libuv) time idle... idle... idle... Simpler code for the user Idle time only in one thread Sched. req1 Sched. req2 Sched. req3 req1 result req3 result req2 result @loige

Slide 18

Slide 18 text

I am oversimplifying a bit… 😛 Watch loige.link/event-loop-what-the-heck if you want to go more in depth! @loige

Slide 19

Slide 19 text

Many ways to handle async flows @loige

Slide 20

Slide 20 text

Delete last reservation if confirmed ● Get a guest object from a guest id (async) ● Get the last reservation from the guest object ● Get the details of that reservation (async) ● Delete that reservation if “confirmed” (async) @loige

Slide 21

Slide 21 text

Callbacks function deleteLastReservationIfConfirmed (client, guestId, cb) { client.getGuest(guestId, (err, guest) => { if (err) { return cb(err) } const lastReservation = guest.reservations.pop() if (typeof lastReservation === 'undefined') { return cb(null, false) } client.getReservation(lastReservation, (err, reservation) => { if (err) { return cb(err) } if (reservation.status === 'confirmed') { client.deleteReservation(reservation.id, (err) => { if (err) { return cb(err) } return cb(null, true) }) } }) }) } @loige

Slide 22

Slide 22 text

Promises function deleteLastReservationIfConfirmed (client, guestId) { return client.getGuest(guestId) .then((guest) => { const lastReservation = guest.reservations.pop() if (typeof lastReservation !== 'undefined') { return client.getReservation(lastReservation) } }) .then((reservation) => { if (!reservation || reservation.status !== 'confirmed') { return false } return client.deleteReservation(reservation.id) }) } @loige

Slide 23

Slide 23 text

Async/Await async function deleteLastReservationIfConfirmed (client, guestId) { const guest = await client.getGuest(guestId) const lastReservation = guest.reservations.pop() if (typeof lastReservation === 'undefined') { return false } const reservation = await client.getReservation(lastReservation) if (!reservation || reservation.status !== 'confirmed') { return false } return client.deleteReservation(reservation.id) } @loige

Slide 24

Slide 24 text

Advantages of Async/Await ● Easier to read and reason about (“sequential flow”) ● Easier to deal with conditional async operations ● Unified error handling (you can catch both synchronous and asynchronous errors) ⚠ To fully understand async/await, you still need to understand callbacks and promises, don’t ignore them! @loige

Slide 25

Slide 25 text

Other ways to handle async ● Events ● Streams ● Async iterators/generators @loige

Slide 26

Slide 26 text

Some interesting async patterns @loige

Slide 27

Slide 27 text

Sequential execution const guestIds = ['Peach', 'Toad', 'Mario', 'Luigi'] for (const guestId of guestIds) { await deleteLastReservationIfConfirmed(client, guestId) } @loige

Slide 28

Slide 28 text

Sequential execution ⚠ Common pitfall const guestIds = ['Peach', 'Toad', 'Mario', 'Luigi'] guestIds.forEach(async (guestId) => { await deleteLastReservationIfConfirmed(client, guestId) }) Don’t use Array.map or Array.forEach! forEach will run all the functions without awaiting them, so all the delete invocations will happen concurrently! @loige

Slide 29

Slide 29 text

Concurrent execution const guestIds = ['Peach', 'Toad', 'Mario', 'Luigi'] await Promise.all( guestIds.map( guestId => deleteLastReservationIfConfirmed(client, guestId) ) ) ⚠ Promise.all rejects as soon as one promise rejects. A failure will result in the failure of the entire operation. @loige

Slide 30

Slide 30 text

Concurrent execution - Alternative const guestIds = ['Peach', 'Toad', 'Mario', 'Luigi'] const results = await Promise.allSettled( guestIds.map( guestId => deleteLastReservationIfConfirmed(client, guestId) ) ) [ { status: 'fulfilled', value: true }, { status: 'fulfilled', value: true }, { status: 'rejected', reason: Error }, { status: 'fulfilled', value: true } ] @loige

Slide 31

Slide 31 text

Concurrent execution - limited const mapLimit = require('async/mapLimit') const guestIds = ['Peach', 'Toad', 'Mario', 'Luigi', '...'] const results = await mapLimit( guestIds, 2, // max concurrency async (guestId) => deleteLastReservationIfConfirmed(client, guestId) ) When you have a lot of tasks to run and what to keep limited concurrency Uses the third-party async module (npm.im/async) @loige

Slide 32

Slide 32 text

Request batching Classic flow - one user @loige HTTP Server DB /availability/v1/units

Slide 33

Slide 33 text

Request batching Classic flow - multiple users (no batching) @loige HTTP Server DB /availability/v1/units /availability/v1/units /availability/v1/units

Slide 34

Slide 34 text

Request batching Classic flow - multiple users (with batching!) @loige HTTP Server DB /availability/v1/units /availability/v1/units /availability/v1/units 📘 Requests in-flight pending fulfilled ⏱ /availability/v1/units ⏱

Slide 35

Slide 35 text

The web server @loige const { createServer } = require('http') const server = createServer(async (req, res) => { const url = new URL(req.url, 'http://localhost') if (url.pathname !== '/availability/v1/units') { res.writeHead(404, 'Not found') return res.end() } const units = await getAvailableUnitsFromDb() res.writeHead(200) res.end(JSON.stringify(units)) }) server.listen(8000)

Slide 36

Slide 36 text

Get units from DB @loige let pendingRequest = null async function getAvailableUnitsFromDb () { if (pendingRequest) { console.log('batching') return pendingRequest } console.log('Getting data from db') pendingRequest = db.query('SELECT * FROM units WHERE "availability" > 0') pendingRequest.finally(() => { pendingRequest = null }) return pendingRequest }

Slide 37

Slide 37 text

Performance comparison @loige Without batching With batching +15% throughput

Slide 38

Slide 38 text

In conclusion ● Node.js is a great runtime for I/O bound applications ● It’s also great for full stack web development ● The async model allows you to express concurrent computation effectively ● You still need to master callbacks, promises and async / await! ● There are many patterns that can help you out to keep your code organised and more performant. @loige

Slide 39

Slide 39 text

Want to learn more? ● nodejsdesignpatterns.com - possibly a great book :) ● loige.link/javascript-mdn - mozilla guides ● eloquentjavascript.net - free e-book ● freecodecamp.org - interactive code training ● nodejs.org/api - Node.js official docs @loige

Slide 40

Slide 40 text

THANK YOU @loige loige.link/devrupt-node loige.link/devrupt-node-code