Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Exploring Async Techniques in Javascript

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →

Exploring Async Techniques in Javascript

Avatar for Guilherme Coelho

Guilherme Coelho

January 24, 2018

Other Decks in Technology

Transcript

  1. grvcoelho @ pagarme Hello! • Guilherme Coelho • Tech Leader

    @ pagarme • https://github.com/grvcoelho 2
  2. grvcoelho @ pagarme func main() { fmt.Println("BEGIN") contents, _ :=

    ioutil.ReadFile("/tmp/dat") fmt.Print(string(contents)) fmt.Println("FINISH") } 3
  3. grvcoelho @ pagarme const fs = require('fs') console.log('BEGIN') fs.readFile('/tmp/dat', (err,

    contents) => { console.log(String(contents)) }) console.log('FINISH') 4
  4. grvcoelho @ pagarme Async • Part of your program runs

    now • Part of your program runs later 5
  5. grvcoelho @ pagarme Featuring • Callbacks • Promises • Generators

    + Coroutines • Async/Await • Reactive Extensions 6
  6. grvcoelho @ pagarme Callbacks • Node.js is built on top

    of them • Fundamental unit of asynchrony in Javascript • Pass a function to another function that will call it back 7
  7. grvcoelho @ pagarme const fs = require('fs') console.log('BEGIN') readFile('/tmp/dat', (err,

    contents) => { console.log(String(contents)) }) console.log('FINISH') 8
  8. grvcoelho @ pagarme const readFile = (filename, callback) => {

    system.openAndReadFile(filename, (err, data) => { if (err) { return callback(err, null) } callback(null, String(data)) }) } readFile('/tmp/dat', (err, contents) => { console.log(contents) }) 9
  9. grvcoelho @ pagarme const divide = (a, b, callback) =>

    { if (b === 0) { return callback('Cannot be zero', null) } const result = a / b callback(null, result) } divide(10, 2, (err, result) => { // ... }) 10
  10. grvcoelho @ pagarme runA(function () { runB() runC(function () {

    runD() }) runE() }) runF() // A -> F -> B -> C -> E -> D 11
  11. grvcoelho @ pagarme runA(function () { // sync runB() runC(function

    () { // sync runD() }) runE() }) runF() // A -> B -> C -> D -> E -> F 12
  12. grvcoelho @ pagarme const finishOrder = (orderId, callback) => {

    getProducts(orderId, (err, products) => { getStock(products, (err, hasStock) => { calculateTotal(products, (err, total) => { getShipping((err, shipping) => { confirmPurchase(orderId, shipping, (err, message) => { callback(null, message) }) }) }) }) }) } finishOrder(123, (err, message) => { // ... }) 13
  13. grvcoelho @ pagarme Callback Hell • Indentation* • Poor error

    handling • Asynchronous flow in nonlinear, nonsequantial way • Inversion of control 14
  14. grvcoelho @ pagarme async.waterfall([ function(callback) { callback(null, 'one', 'two') },

    function(arg1, arg2, callback) { // arg1 now equals 'one' and arg2 now equals 'two' callback(null, 'three') }, function(arg1, callback) { // arg1 now equals 'three' callback(null, 'done') }, ], function (err, result) { // result now equals 'done' }) 17
  15. grvcoelho @ pagarme Callback Problems • Sync and async callbacks

    • Error handling + Error swallowing • Calling the callback multiple times • Never calling the callback 19
  16. grvcoelho @ pagarme Promises • A representation of a future

    value • Time-independent container/wrapper of a value • The Hamburger Analogy 20
  17. grvcoelho @ pagarme buyHamburger() .then(hamburger => { return eatHamburger(hamburger) })

    .catch(err => { console.log(err) return goHomeChateado() }) 21
  18. grvcoelho @ pagarme Promises • Have 3 states: Pending, Fulfilled,

    Rejected • The state never changes from Fulfilled or Rejected • Can only be resolved once 22
  19. grvcoelho @ pagarme Promises • Promises are chainable • Promises

    are always async • Every .then call returns a new Promise • Rejected values go until they find a .catch 24
  20. grvcoelho @ pagarme const finishOrder = (orderId, callback) => {

    getProducts(orderId, (err, products) => { getStock(products, (err, hasStock) => { calculateTotal(products, (err, total) => { getShipping((err, shipping) => { confirmPurchase(orderId, shipping, (err, message) => { callback(null, message) }) }) }) }) }) } finishOrder(123, (err, message) => { // ... }) 25
  21. grvcoelho @ pagarme const finishOrder = (orderId) => { return

    getProducts(orderId) .then(getStock) .then(calculateTotal) .then(getShipping) .then(confirmPurchase) .catch(handleErrors) } finishOrder(123) .then(message => { displayThankYouPage() }) 26
  22. grvcoelho @ pagarme const calculateTotal = (product, quantity) => {

    return new Promise((resolve, reject) => { fetch(‘/${product.id}/price', (err, price) => { if (err) { return reject(err) } const total = price * quantity return resolve(total) }) }) } 27
  23. grvcoelho @ pagarme Promise vs Callback Problems • Sync and

    async callbacks • Error handling + Error swallowing • Calling the callback multiple times • Never calling the callback 29
  24. grvcoelho @ pagarme Promise.resolve(5) .then(x => { return x *

    2 }) .then(result => { console.log(result) return anotherPromise() }) console.log(‘Hello!') 31 Promise.resolve
  25. grvcoelho @ pagarme Promise.resolve(5) .then(x => { if (x ===

    5) { return Promise.reject( new Error('Value not allowed’) ) } else if (Number.isNaN(Number(x))) { throw new TypeError('Must be a number') } }) .catch(err => { console.log(err.message) }) 32 Promise.reject
  26. grvcoelho @ pagarme const timeout = (ms) => new Promise((resolve,

    reject) => { setTimeout( () => reject(new TimeoutError()), ms ) }) Promise.race([ timeout(3000), chargeCreditCard() ]) .then(displaySuccess) .catch(handleTimeout) 34
  27. grvcoelho @ pagarme findOrder(123) .then(order => { return findItems(order) })

    .then(items => { return findCustomer(order) // no order here }) 36 Problems with scope
  28. grvcoelho @ pagarme findOrder(123) .then(order => { return findItems(order) .then(items

    => { return findCustomer(order) .then(customer => { return chargeOrder(order, items, customer) }) }) }) 37 Problems with scope
  29. grvcoelho @ pagarme findOrder(123) .then(order => { return Promise.all([order, findItems(order)])

    }) .then(([order, items]) => { return Promise.all([ order, items, findCustomer(order) ]) }) .then(([order, items, customer]) => { return chargeOrder(order, items, customer) }) 38 Problems with scope
  30. grvcoelho @ pagarme const handleOrder = (orderId) => { const

    order = findOrder(orderId) const items = findItems(order) const customer = findCustomer(order) return chargeOrder(order, items, customer) } 39
  31. grvcoelho @ pagarme const handleOrder = orderId => coroutine(function*() {

    const order = yield findOrder(orderId) const items = yield findItems(order) const customer = yield findCustomer(order) return chargeOrder(order, items, customer) }) 40
  32. grvcoelho @ pagarme Generators • Supported since Node 4.8.x •

    Make it possible to pause and resume function execution • Change of control between generator and iterator 41
  33. grvcoelho @ pagarme function* gen () { console.log('Inside generator') yield

    'Press next' const name = yield return `Hello ${name}` } const it = gen() it.next() // { done: false, value: 'Press next' } it.next() // { done: false, value: undefined } it.next(‘Gui') // { done: true, value: "Hello Gui" } 42
  34. grvcoelho @ pagarme Coroutines • Use generators to “stop” functions

    • Resume functions when promises are resolved • Write async code that flows like sync 43
  35. grvcoelho @ pagarme const p = coroutine(function* () { const

    a = yield Promise.resolve(5) const b = yield (a * 2) return a + b // 15 }) 44
  36. grvcoelho @ pagarme const coroutine = (generator) => { const

    it = generator() const next = (data) => { const message = it.next(data) if (!message.done) { return Promise.resolve(message.value) .then(next) } return Promise.resolve(message.value) } return next() } 45
  37. grvcoelho @ pagarme const handleOrder = orderId => coroutine(function*() {

    const order = yield findOrder(orderId) const items = yield findItems(order) const customer = yield findCustomer(order) return chargeOrder(order, items, customer) }) 46
  38. grvcoelho @ pagarme const handleOrder = order => coroutine(function* ()

    { const items = yield findItems(order) const customer = yield findCustomer(order) return chargeOrder(order, items, customer) }) // vs const handleOrder = (order) => { return Promise.all([ findItems(order), findCustomer(order) ]) .then(([items, customer]) => { return chargeOrder(order, items, customer) }) } 47
  39. grvcoelho @ pagarme const handleOrder = coroutine(function* (orderId) { const

    order = yield findOrder(orderId) const [items, customer] = yield Promise.all([ findItems(order), findCustomer(order) ]) return chargeOrder(order, items, customer) }) 48
  40. grvcoelho @ pagarme Coroutines @ pagarme • Used in some

    controllers and services • Mostly used on tests • Specially used in e2e tests 49
  41. grvcoelho @ pagarme let response before(() => { return request(‘/orders').then(response

    => { response = response }) }) it('should test request', () => { assert(response.status).is(200) }) // vs it('should test request', coroutine(function* () { const response = yield request('/orders') assert(response.status).is(200) })) 50
  42. grvcoelho @ pagarme Async/Await • Supported since Node 7.6.x •

    Works just like Generators + Coroutines • Is built on top of Promises, returns Promises • Write async code that flows like sync 51
  43. grvcoelho @ pagarme const handleOrder = coroutine(function* (orderId) { const

    order = yield findOrder(orderId) const items = yield findItems(order) const customer = yield findCustomer(order) return chargeOrder(order, items, customer) }) 52
  44. grvcoelho @ pagarme const handleOrder = async function (orderId) {

    const order = await findOrder(orderId) const items = await findItems(order) const customer = await findCustomer(order) return chargeOrder(order, items, customer) } 53
  45. grvcoelho @ pagarme const fn = async function () {

    const values = [2, 5] const result = values.map(async function (x) { return await double(x) }) return result // [Promise{}, Promise{}] } 54
  46. grvcoelho @ pagarme const fn = async function () {

    const values = [2, 5] const promises = values.map(x => double(x)) const result = await Promise.all(promises) return result // [4, 10] } 55
  47. grvcoelho @ pagarme Reactive Programming • Asynchronous streams of events/data

    • Intervals, webSockets, DOM events • Single vs Multiple values • Cancellable vs Uncancellable • Eager vs lazy 56
  48. grvcoelho @ pagarme const numberPromise = new Promise((resolve, reject) =>

    { resolve(42) resolve(100) }) numberPromise.then(value => console.log(value)) 57
  49. grvcoelho @ pagarme const numberObservable = Observable.create((observer) => { observer.next(42)

    observer.error() observer.completed() }) numberObservable.subscribe(value => console.log(value)) 58
  50. grvcoelho @ pagarme const Rx = require('rx') const observable =

    Rx.Observable.interval(1000) .map(x => x + 1) .takeWhile(x => x <= 3) .concat(Rx.Observable.of('World')) observable.subscribe(x => console.log(`Hello ${x}!`)) // Hello 1 // Hello 2 // Hello 3 // Hello World 59
  51. grvcoelho @ pagarme const clickStream = Rx.Observable.fromEvent( button, ‘click’ )

    const multiClickStream = clickStream .buffer(() => clickStream.throttle(250)) .map((list) => list.length) .filter(len => len >= 2) .subscribe(() => alert(‘Double Click!’)) 60
  52. grvcoelho @ pagarme Conclusions • Don’t use callbacks* • Promises

    “resolve” almost all your problems. Use it by default • Use coroutines when you can’t use async/ await • Use async/await to resolve some scope issues • Reactive Programming is worth your attention 61