Asynchronous & Synchronous JavaScript. There and back again.

Asynchronous & Synchronous JavaScript. There and back again.

6e98c4f0f46eb0bf48148e83067a4391?s=128

Maciej Treder

October 17, 2019
Tweet

Transcript

  1. @maciejtreder

  2. single thread == single action

  3. function saySomethingNice():void { console.log(`Nice to meet you.`); } function greet(name:

    string): void { console.log(`Hello ${name}!`); saySomethingNice(); } greet(`John`); Hello John! Nice to meet you.
  4. function saySomethingNice():void { console.log(`Nice to meet you.`); } function greet(name:

    string): void { setTimeout(() => console.log(`Hello ${name}!`, 0)); saySomethingNice(); } greet(`John`); Nice to meet you. Hello John!
  5. Those are not part of JavaScript • setTimeout • setInterval

    • fetch • DOM • window
  6. Event Loop greet() stack setTimeout API callbacks queue main() function

    saySthNice():void { } function greet(name: string): void { saySthNice(); } greet(`John`); setTimeout() saySthNice() console.log() console.log() setTimeout(() => console.log(`Hello ${name}!`, 0)); saySthNice(); console.log(`Nice to meet you.`); Nice to meet you. Hello John!
  7. Case study maciejtreder.github.io/asynchronous-javascript/ [ { "id": 1, "name": "James Cameron"

    }, { "id": 2, "name": "Quentin Tarantino" }, { "id": 3, "name": "Wes Anderson" }, { "id": 4, "name": "Stanley Kubrick" } ] [ { "id": 4, "title": "Django Unchained" }, { "id": 5, "title": "Inglourious Basterds" }, { "id": 6, "title": "Grindhouse" } ] { "id": 1, "reviewer": "Andy", "rating": 10 }, { "id": 2, "reviewer": "Margaret", "rating": 5 }, { "id": 3, "reviewer": "Bridget", "rating": 8 } ] /directors /directors/{id}/movies /movies/{id}/reviews
  8. const request = require('request'); request(`https://maciejtreder.github.io/asynchronous-javascript/ directors`, {json: true}, (err, res,body)

    => { console.log(body); }); [ { id: 1, name: 'James Cameron' }, { id: 2, name: 'Quentin Tarantino' }, { id: 3, name: 'Wes Anderson' }, { id: 4, name: 'Stanley Kubrick' } ]
  9. const request = require('request'); }); }); }); }); request(`https://maciejtreder.github.io/asynchronous-javascript/ directors`,

    {json: true}, (err, res, directors) => { let tarantinoId = directors.find(director => director.name === "Quentin Tarantino").id; request(`https://maciejtreder.github.io/asynchronous-javascript/directors/${tarantinoId}/ movies`, {json: true}, (err, res, movies) => { let checkedMoviesCount = 0; movies.forEach(movie => { request(`https://maciejtreder.github.io./asynchronous-javascript/movies/${movie.id}/ reviews`, {json: true}, (err, res, reviews) => { checkedMoviesCount++; aggregatedScore = 0; reviews.forEach(review => aggregatedScore += review.rating); movie.averageRating = aggregatedScore / reviews.length; if (checkedMoviesCount === movies.length) { movies.sort((m1, m2) => m2.averageRating - m1.averageRating); console.log(`The best movie by Quentin Tarantino is... ${movies[0].title} !!!`); }
  10. 3 REST calls & 8 nested levels

  11. request(`https://maciejtreder.github.io/asynchronous-javascript/ directors`, {json: true}, (err, res, body) => findDirector(body, 'Quentin

    Tarantino’)); function findDirector(directors, name) { let directorId = directors.find(director => director.name === name).id; request(`https://maciejtreder.github.io/asynchronous-javascript/directors/${directorId}/ movies`, {json: true}, (err, res, body) => getReviews(body, name)); } function getReviews(movies, director) { let checkedSoFar = {count: 0}; movies.forEach(movie => { request(`https://maciejtreder.github.io/asynchronous-javascript/movies/${movie.id}/ reviews`, {json: true}, (err, res, body) => calculateAverageScore(body, movie, director, checkedSoFar, movies )); }); } function calculateAverageScore(reviews, movie, director, checkedSoFar, movies) { checkedSoFar.count++; aggregatedScore = 0; count = 0; reviews.forEach(review => { aggregatedScore += review.rating; count++; }); movie.averageRating = aggregatedScore / count; if (checkedSoFar.count == movies.length) { movies.sort((m1, m2) => m2.averageRating - m1.averageRating); console.log(`The best movie by ${director} is... ${movies[0].title} !!!`); } }
  12. const request = require('request'); function calculateAverageScore(reviews, movie, director, checkedSoFar, movies)

    { checkedSoFar.count++; aggregatedScore = 0; count = 0; reviews.forEach(review => { aggregatedScore += review.rating; count++; }); movie.averageRating = aggregatedScore / count; if (checkedSoFar.count == movies.length) { movies.sort((m1, m2) => m2.averageRating - m1.averageRating); console.log(`The best movie by ${director} is... ${movies[0].title} !!!`); } } function getReviews(movies, director) { let checkedSoFar = {count: 0}; movies.forEach(movie => { request(`https://maciejtreder.github.io/asynchronous-javascript/movies/${movie.id}/ reviews`, {json: true}, (err, res, body) => calculateAverageScore(body, movie, director, checkedSoFar, movies)); }); } function findDirector(directors, name) { let directorId = directors.find(director => director.name === name).id; request(`https://maciejtreder.github.io/asynchronous-javascript/directors/${directorId}/ movies`, {json: true}, (err, res, body) => getReviews(body, name)); } request(`https://maciejtreder.github.io/asynchronous-javascript/ directors`, {json: true}, (err, res, body) => findDirector(body, 'Quentin Tarantino'));
  13. Promises const promise1 = new Promise<string>(resolve => { setTimeout(() =>

    resolve(`Value from promise 1`), 1000); }); promise1.then(value => console.log(`Promise resolved with value: ${value}`)); promise1.then(value => console.log(`One more time ${value}`)); Promise resolved with value: Value from promise 1 One more time Value from promise 1
  14. Promises const promise1 = new Promise<string>(resolve => { resolve(`Value from

    promise 1`); resolve(`Promise resolves only once!`); }); promise1.then(value => console.log(`Promise resolved with value: ${value}`)); Promise resolved with value: Value from promise 1
  15. Promises const promise2 = new Promise<string>((resolve, reject) => { reject({reason:

    `Reject type doesn't depend on the Promise type, and it's always any.`}); }); promise2.then(value => {}, rejection => console.log(`Handled rejection`, rejection)); promise2.then(value => {}).catch(rejection => console.log(`Another way of handling rejection`, rejection)); Handled rejection { reason: 'Reject type doesn\'t depend on the Promise type, and it\'s always any.’} Another way of handling rejection { reason: 'Reject type doesn\'t depend on the Promise type, and it\'s always any.’}
  16. Promises const promise2 = new Promise<string>((resolve) => { throw {reason:

    `Reject type doesn't depend on the Promise type, and it's always any.`}; }); promise2.then(value => {}, rejection => console.log(`Handled rejection`, rejection)); promise2.then(value => {}).catch(rejection => console.log(`Another way of handling rejection`, rejection)); Handled rejection { reason: 'Reject type doesn\'t depend on the Promise type, and it\'s always any.’} Another way of handling rejection { reason: 'Reject type doesn\'t depend on the Promise type, and it\'s always any.’}
  17. This won’t work const promise2 = new Promise<string>((resolve, reject) =>

    { reject({reason: `Reject type doesn't depend on the Promise type, and it's always any.`}); }); promise2.then(value => {}); //unhandled rejection try { promise2.then(value => {}); } catch(error) { console.log(`Catched error`); } (node:14312) UnhandledPromiseRejectionWarning: #<Object>
  18. Chaining promise1 .then(val => square(val)) .then(value => console.log(value)); 9 const

    promise1 = Promise.resolve(3); const square = (val) => Promise.resolve(val * val); promise1.then(value => { square(value).then(value => { console.log(value); }); }); ==
  19. Chaining promise1 .then(val => square(val)) .then(() => {throw {};}) .then(val

    => console.log(val)) .catch(error => console.log(`Catched`)) .finally(() => console.log(`Finally statement`)); Catched Finally statement
  20. Chaining promise1 .then(val => square(val)) .then(() => {throw null;}) .catch(error

    => { console.log(`Error in second promise`) return 1; // return error; }) .then(val => console.log(val)) .catch(error => console.log(`Catched: ${error}`)) .finally(() => console.log(`Finally statement`)); Error in second promise 1 Finally statement
  21. Combining const promise1 = Promise.resolve(3); const square = (val) =>

    Promise.resolve(val * val); const promise2 = Promise.reject('Rejecting that!'); Promise.all([promise1, square(2)]).then(val => console.log(val)); Promise.all([promise1, square(2), promise2]) .then(val => console.log(val)) .catch(error => console.log(`Failed because of: ${error}`)); [ 3, 4 ] Failed because of: Rejecting that!
  22. Combining const promise1 = Promise.resolve(3); const square = (val) =>

    Promise.resolve(val * val); const promise2 = Promise.reject('Rejecting that!'); Promise.all([ promise1.catch(() => undefined), square(2).catch(() => undefined ), promise2.catch(() => undefined ) ]) .then(val => console.log(val)) .catch(error => console.log(`Failed because of: ${error}`)); [ 3, 4, undefined ]
  23. Combining const allSettled = require('promise.allsettled'); allSettled.shim(); const promise1 = Promise.resolve(3);

    const square = (val) => Promise.resolve(val * val); const promise2 = Promise.reject('Rejecting that!'); Promise.allSettled([promise1, square(2), promise2]) .then(val => console.log(val)) .catch(error => console.log(`Failed because of: ${error}`)); [ { status: 'fulfilled', value: 3 }, { status: 'fulfilled', value: 4 }, { status: 'rejected', reason: 'Rejecting that!' } ]
  24. Combining const promise1 = Promise.resolve(3); const square = (val) =>

    Promise.resolve(val * val); const promise2 = Promise.reject('Rejecting that!'); Promise.race([promise1, square(2), promise2]) .then(val => console.log(val)) .catch(error => console.log(`Failed because of: ${error}`)); 3
  25. Combining function watchDog() { return new Promise(resolve => setTimeout(resolve(), 2000));

    } const promise1 = Promise.resolve(3); const square = (val) => Promise.resolve(val * val); const promise2 = Promise.reject('Rejecting that!'); Promise.race([ promise2.catch(() => {/* noop */}), square(2), promise1, watchDog() ]) .then(val => console.log(val)) .catch(error => console.log(`Failed because of: ${error}`)); 3
  26. const fetch = require('node-fetch'); fetch(`https://maciejtreder.github.io/asynchronous-javascript/directors/`) .then(response => response.json()) .then(directors =>

    { let tarantinoId = directors.find(director => director.name === "Quentin Tarantino").id; return fetch(`https://maciejtreder.github.io/asynchronous-javascript/directors/${tarantinoId}/ movies`); }) .then(response => response.json()) .then(movies => { let reviewsArr = []; movies.forEach(movie => { reviewsArr.push( fetch(`https://maciejtreder.github.io/asynchronous-javascript/movies/${movie.id}/ reviews`) .then(response => response.json()).then(reviews => { return {movie: movie, reviews: reviews}; }) ); }); return Promise.all(reviewsArr); }) .then(reviewSets => { let moviesAndRatings = []; reviewSets.forEach(reviews => { let aggregatedScore = 0; reviews.reviews.forEach( review => aggregatedScore += review.rating ); let averageScore = aggregatedScore / reviews.reviews.length; moviesAndRatings.push({title: reviews.movie, averageScore: averageScore}); }); return moviesAndRatings.sort((m1, m2) => m2.averageScore - m1.averageScore)[0].title; }) .then(movie => console.log(` ${movie.title} !`))
  27. fetch(`/directors/`) /directors /directors/2/movies .then(directors => { let tarantinoId = directors.findTarantino();

    return fetch(`/directors/${tarantinoId}/movies`); }) /movies/{id}/reviews .then(movies => { let reviewsArr = fetchAllReviews(movies); return Promise.all(reviewsArr); }) .then(reviewSets => { let moviesAndRatings = calculateAverageScores(); return moviesAndRatings.sort((m1, m2) => m2.aver ageScore - m1.averageScore)[0].title; }) .then(movie => console.log(`The best movie by Quen tin Tarantino is... ${movie.title}!`));
  28. import { Observable, Observer } from 'rxjs'; const clock$: Observable<string>

    = Observable.create((observer: Observer<string>) => { console.log('In Observable'); setInterval(() => { observer.next('tick'); }, 1000); }); RxJS clock$.subscribe(val => console.log(val)); In Observable; tick; tick; tick; tick; …
  29. RxJS const clock$: Subject<string> = new Subject<string>(); const interval =

    setInterval(() => { clock$.next('tick'); }, 1000); tick; tick setTimeout(() => clock$.complete(), 8 * 1000); setTimeout(() => clearInterval(interval), 9 * 1000); let subscription = clock$.subscribe(val => console.log(val)); setTimeout(() => subscription.unsubscribe(), 3 * 1000); setTimeout(() => subscription = clock$.subscribe(val => console.log(val)), 4*1000); tick; tick; tick; tick
  30. Manipulating data const clock$: Subject<string> = new Subject<string>(); const interval

    = setInterval(() => { clock$.next('tick'); }, 1000); setTimeout(() => clock$.complete(), 8 * 1000) setTimeout(() => clearInterval(interval), 9 * 1000); tick; tock; tick; tock; tick; tick; tock; tick clock$.pipe( map((val, index) => index % 2 == 0 ? val : 'tock') ).subscribe(val => console.log(val)); import { map } from 'rxjs/operators';
  31. https://xgrommx.github.io/rx-book/content/ which_operator_do_i_use/index.html

  32. Error catching import { , Subject } from 'rxjs'; import

    { map, } from ‘rxjs/operators'; const clock$: Subject<string> = new Subject<string>(); const interval = setInterval(() => { clock$.next('tick'); }, 1000); setTimeout(() => clock$.complete(), 8 * 1000); setTimeout(() => clearInterval(interval), 9 * 1000); clock$.pipe( map((val, index) => index % 2 == 0 ? val : ‘tock’), ).subscribe(val => console.log(val)); tick; tock; tick; tock; Error: BOOOM! setTimeout(() => clock$.error(new Error('BOOOM!')), 5 * 1000); setTimeout(() => console.log('Still alive?'), 12 * 1000); catchError(error => of('Explosion!')) catchError of Explosion! Still alive?
  33. RxJS 12 6 3 9 1 2 4 5 7

    8 10 11 tick tick tick tick tick tick tick tick tick tick tick tock tick tock tick tock tick tock BOOM! Explosion! Still alive? import { of, Subject } from 'rxjs'; import { map, catchError } from ‘rxjs/operators'; const clock$: Subject<string> = new Subject<string>(); const interval = setInterval(() => { clock$.next('tick'); }, 1000); setTimeout(() => clock$.error(new Error('BOOOM!')), 5 * 1000) setTimeout(() => clearInterval(interval), 6 * 1000); clock$.pipe( map((val, index) => index % 2 == 0 ? val : 'tock'), catchError(error => of('Explosion!')) ).subscribe(val => console.log(val)); setTimeout(() => console.log('Still alive?'), 12 * 1000);
  34. RxJS & REST const Axios = require('axios-observable').Axios; const map =

    require('rxjs/operators').map; const flatMap = require('rxjs/operators').flatMap; const combineLatest = require('rxjs').combineLatest; Axios.get('https://maciejtreder.github.io/asynchronous-javascript/directors/') .pipe( map(response => response.data), map(response => response.find(director => director.name == 'Quentin Tarantino').id), flatMap(id => Axios.get(`https://maciejtreder.github.io/asynchronous-javascript/directors/ ${id}/movies`)), map(response => response.data), flatMap(movies => { const observables$ = []; movies.forEach(movie => { observables$.push( Axios.get(`https://maciejtreder.github.io/asynchronous-javascript/movies/$ {movie.id}/reviews`) .pipe( map(response => response.data), map(reviews => { let count = 0; reviews.forEach(review => count++); return sum/reviews.length; }), map(average => { return {title: movie.title, score: average}; }) ) ); }); return combineLatest(observables$); }), map(movies => movies.sort((movie1, movie2) => movie2.score - movie1.score)), map(movies => movies[0].title) ) .subscribe(console.log);
  35. RxJS pros const Axios = require('axios-observable').Axios; const map = require('rxjs/operators').map;

    const retry = require('rxjs/operators').retry; Axios.get('https://maciejtreder.github.io/asynchronous-javascript/directors/123') .pipe( map(resp => resp.data) ) .subscribe(console.log); retry(3),
  36. callback, then(), subscribe() …

  37. Make the code synchronous again const getMultiplier = Promise.resolve(2); const

    multiply = (multiply, multiplier) => Promise.resolve(multiply * multiplier); let multiplier = getMultiplier; let result = multiply(10, multiplier); console.log(`Multiply result: ${result}`); getMultiplier .then(value => multiply(10, value)) .then(result => console.log(`Multiply result: ${result}`)); await await var multiplier = await getMultiplier; ^^^^^ SyntaxError: await is only valid in async function at new Script (vm.js:79:7) at createScript (vm.js:251:10) at Object.runInThisContext (vm.js:303:10) at Module._compile (internal/modules/cjs/loader.js:657:28) at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) at Function.Module.runMain (internal/modules/cjs/loader.js:742:12) at startup (internal/bootstrap/node.js:283:19)
  38. Make the code asynchronous back! let multiplier = getMultiplier; let

    result = multiply(10, multiplier); console.log(`Multiply result: ${result}`); await await (async () => { })(); async function myFunction() { return "someValue"; } == function myFunction() { return Promise.resolve("someValue"); } Multiply result: 20 const getMultiplier = Promise.resolve(2); const multiply = (multiply, multiplier) => Promise.resolve(multiply * multiplier);
  39. vs let multiplier = getMultiplier; let result = multiply(10, multiplier);

    console.log(`Multiply result: ${result}`); await await (async () => { })(); getMultiplier .then(value => multiply(10, value)) .then(result => console.log(`Multiply result: ${result}`));
  40. async-await and RxJS import { Subject } from 'rxjs'; const

    clock$: Subject<number> = new Subject<number>(); let i = 0; const interval = setInterval(() => { clock$.next(i++); }, 1000); setTimeout(() => clock$.complete(), 4*1000); setTimeout(() => clearInterval(interval), 5 * 1000); (async () => { console.log(await clock$.toPromise()); })(); 2
  41. async-await error catching const promise = Promise.reject('rejection reason'); (async ()

    => { try { console.log(await promise); } catch (err) { console.log(`Error catched: ${err}`); } finally { console.log('Finally executes'); } })(); Error catched: rejection reason Finally executes
  42. Performance pitfall function promiseFunction1() { return new Promise(resolve => {

    setTimeout(() => { resolve("abc"); }, 2000); }); } function promiseFunction2() { return new Promise(resolve => { setTimeout(() => { resolve("abc"); }, 2000); }); } (async () => { console.log(await promiseFunction1()); console.log(await promiseFunction2()); })(); (async () => { const promisefn1 = promiseFunction1(); const promisefn2 = promiseFunction2(); console.log(await promisefn1); console.log(await promisefn2); })(); 2 sec 4 sec
  43. const fetch = require('node-fetch'); (async () => { })(); const

    directors = await fetch(`https://maciejtreder.github.io/asynchronous-javascript/directors/`) .then(response => response.json()); const tarantinoId = directors.find(director => director.name === "Quentin Tarantino").id; const quentinMovies = await fetch(`https://maciejtreder.github.io/asynchronous-javascript/ directors/${tarantinoId}/movies`) .then(response => response.json()); const reviewsCall = []; quentinMovies.forEach(movie => { reviewsCall.push(fetch(`https://maciejtreder.github.io/asynchronous-javascript/movies/$ {movie.id}/reviews`) .then(resp => resp.json()) .then(resp => { return {title: movie.title, reviews: resp}; })); }); const reviews = await Promise.all(reviewsCall); reviews.forEach(entry => { let aggregatedScore = 0; entry.reviews.forEach(review => aggregatedScore += review.rating); entry.score = aggregatedScore / entry.reviews.length; }); const best = reviews.sort((movie1, movie2) => movie2.score - movie1.score)[0].title; console.log(`The best movie by Quentin Tarantino is ${best}`);
  44. Callbacks RxJS Promises async-await asynchronous synchronous? asynchronous asynchronous repeatable one-shot

    repeatable (not) reusable reusable reusable (not) manipulatable manipulatable manipulatable REST WebSocket Dependent operations DOM events
  45. Resources • https://www.twilio.com/blog/asynchronous-javascript-understanding-callbacks • https://www.twilio.com/blog/asynchronous-javascript-organize-callbacks-readability- reusability • https://www.twilio.com/blog/asynchronous-javascript-introduction-promises • https://www.twilio.com/blog/asynchronous-javascript-advanced-promises-chaining-

    collections-nodejs • https://www.twilio.com/blog/asynchronous-javascript-refactor-callbacks-promises-node-js
  46. Feedback http://bit.ly/2VQ3qI0

  47. @maciejtreder