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

Asynchronous & Synchronous JavaScript. There and back again.

Asynchronous & Synchronous JavaScript. There and back again.

Maciej Treder

October 17, 2019
Tweet

More Decks by Maciej Treder

Other Decks in Programming

Transcript

  1. @maciejtreder

    View full-size slide

  2. single thread == single action

    View full-size slide

  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.

    View full-size slide

  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!

    View full-size slide

  5. Those are not part of JavaScript
    • setTimeout
    • setInterval
    • fetch
    • DOM
    • window

    View full-size slide

  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!

    View full-size slide

  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

    View full-size slide

  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' } ]

    View full-size slide

  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} !!!`);
    }

    View full-size slide

  10. 3 REST calls & 8 nested levels

    View full-size slide

  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} !!!`);
    }
    }

    View full-size slide

  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'));

    View full-size slide

  13. Promises
    const promise1 = new Promise(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

    View full-size slide

  14. Promises
    const promise1 = new Promise(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

    View full-size slide

  15. Promises
    const promise2 = new Promise((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.’}

    View full-size slide

  16. Promises
    const promise2 = new Promise((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.’}

    View full-size slide

  17. This won’t work
    const promise2 = new Promise((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: #

    View full-size slide

  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);
    });
    });
    ==

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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!

    View full-size slide

  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 ]

    View full-size slide

  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!' } ]

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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} !`))

    View full-size slide

  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}!`));

    View full-size slide

  28. import { Observable, Observer } from 'rxjs';
    const clock$: Observable = Observable.create((observer: Observer) => {
    console.log('In Observable');
    setInterval(() => {
    observer.next('tick');
    }, 1000);
    }); RxJS
    clock$.subscribe(val => console.log(val));
    In Observable; tick; tick; tick; tick; …

    View full-size slide

  29. RxJS
    const clock$: Subject = new Subject();
    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

    View full-size slide

  30. Manipulating data
    const clock$: Subject = new Subject();
    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';

    View full-size slide

  31. https://xgrommx.github.io/rx-book/content/
    which_operator_do_i_use/index.html

    View full-size slide

  32. Error catching
    import { , Subject } from 'rxjs';
    import { map, } from ‘rxjs/operators';
    const clock$: Subject = new Subject();
    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?

    View full-size slide

  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 = new Subject();
    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);

    View full-size slide

  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);

    View full-size slide

  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),

    View full-size slide

  36. callback, then(), subscribe() …

    View full-size slide

  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)

    View full-size slide

  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);

    View full-size slide

  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}`));

    View full-size slide

  40. async-await and RxJS
    import { Subject } from 'rxjs';
    const clock$: Subject = new Subject();
    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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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}`);

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  46. Feedback
    http://bit.ly/2VQ3qI0

    View full-size slide

  47. @maciejtreder

    View full-size slide