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

Asynchronous JavaScript - there and back again

Asynchronous JavaScript - there and back again

Maciej Treder

June 10, 2020
Tweet

More Decks by Maciej Treder

Other Decks in Technology

Transcript

  1. @maciejtreder
    Asynchronous and Synchronous JavaScript. There and back again.

    View Slide

  2. single thread == single action

    View 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 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 Slide

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

    View Slide

  6. Event Loop
    stack
    callbacks queue
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);

    View Slide

  7. Event Loop
    stack
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);

    View Slide

  8. Event Loop
    greet()
    stack
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);

    View Slide

  9. Event Loop
    greet()
    stack
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    setTimeout()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);

    View Slide

  10. Event Loop
    greet()
    stack setTimeout API
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    setTimeout()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);

    View Slide

  11. Event Loop
    greet()
    stack setTimeout API
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    setTimeout()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);

    View Slide

  12. Event Loop
    greet()
    stack setTimeout API
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    setTimeout()
    console.log() setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);

    View Slide

  13. Event Loop
    greet()
    stack setTimeout API
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    setTimeout()
    console.log()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);

    View Slide

  14. Event Loop
    greet()
    stack setTimeout API
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    console.log()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);

    View Slide

  15. Event Loop
    greet()
    stack setTimeout API
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    saySthNice()
    console.log()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);

    View Slide

  16. Event Loop
    greet()
    stack setTimeout API
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    saySthNice()
    console.log()
    console.log()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);

    View Slide

  17. Event Loop
    greet()
    stack setTimeout API
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    saySthNice()
    console.log()
    console.log()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);
    Nice to meet you.

    View Slide

  18. Event Loop
    greet()
    stack setTimeout API
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    saySthNice()
    console.log()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);
    Nice to meet you.

    View Slide

  19. Event Loop
    greet()
    stack setTimeout API
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    console.log()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);
    Nice to meet you.

    View Slide

  20. Event Loop
    stack setTimeout API
    callbacks queue
    main()
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    console.log()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);
    Nice to meet you.

    View Slide

  21. Event Loop
    stack setTimeout API
    callbacks queue
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    console.log()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);
    Nice to meet you.

    View Slide

  22. Event Loop
    stack setTimeout API
    callbacks queue
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    console.log()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);
    Nice to meet you.

    View Slide

  23. Event Loop
    stack setTimeout API
    callbacks queue
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    console.log()
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);
    Nice to meet you.
    Hello John!

    View Slide

  24. Event Loop
    stack setTimeout API
    callbacks queue
    function saySthNice():void {
    }
    function greet(name: string): void {
    saySthNice();
    }
    greet(`John`);
    setTimeout(() => console.log(`Hello ${name}!`, 0));
    saySthNice();
    console.log(`Nice to meet you.`);
    Nice to meet you.
    Hello John!

    View Slide

  25. 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 Slide

  26. 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 Slide

  27. const request = require('request');
    });
    });
    });
    });

    View Slide

  28. 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;

    View Slide

  29. 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;

    View Slide

  30. 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) => {

    View Slide

  31. 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;

    View Slide

  32. 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 Slide

  33. 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 Slide

  34. 3 REST calls & 8 nested levels

    View Slide

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

    View Slide

  36. 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));
    }

    View Slide

  37. 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
    ));
    });
    }

    View Slide

  38. const request = require('request');
    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
    ));
    });
    }

    View Slide

  39. const request = require('request');
    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 Slide

  40. 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 Slide

  41. 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 Slide

  42. Understanding callbacks
    https://www.twilio.com/blog/asynchronous-javascript-understanding-callbacks

    View Slide

  43. Organizing Callbacks for Readability and Reusability
    https://www.twilio.com/blog/asynchronous-javascript-organize-callbacks-readability-reusability

    View Slide

  44. Promises

    View Slide

  45. 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 Slide

  46. 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 Slide

  47. 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 Slide

  48. 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 Slide

  49. 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 Slide

  50. 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 Slide

  51. 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 Slide

  52. Chaining
    promise1
    .then(val => square(val))
    .then(() => {throw null;})
    .catch(error => {
    console.log(`Error in second promise`)
    return 1;
    })
    .then(val => console.log(val))
    .catch(error => console.log(`Catched: ${error}`))
    .finally(() => console.log(`Finally statement`));
    Error in second promise
    1
    Finally statement

    View Slide

  53. 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 Slide

  54. 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 Slide

  55. 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 Slide

  56. 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 Slide

  57. Combining
    function watchDog() {
    return new Promise((resolve, reject) => setTimeout(() => reject(), 2000));
    }
    const promise1 = Promise.resolve(3);
    const square = (val) => Promise.resolve(val * val);
    const promise2 = Promise.reject('Rejecting that!');
    Promise.race([
    promise2.catch(() => new Promise()),
    square(2),
    promise1,
    watchDog()
    ])
    .then(val => console.log(val))
    .catch(_ => console.log(`Non of the promises resolves before watchdog`));
    3

    View Slide

  58. const fetch = require('node-fetch');
    fetch(`https://maciejtreder.github.io/asynchronous-javascript/directors/`)
    .then(response => response.json())
    List of directors

    View Slide

  59. 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())
    Quentin Movies

    View Slide

  60. 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);
    })
    [{title: movie, reviews: []}]

    View Slide

  61. 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);
    })
    [{title: movie, reviews: []}]

    View Slide

  62. 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} !`))
    [{title: movie,
    averageScore: score}]

    View Slide

  63. 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(reviewSets);
    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 Slide

  64. Introduction to JavaScript Promises
    https://www.twilio.com/blog/asynchronous-javascript-introduction-promises

    View Slide

  65. Advanced Promises with Node.js
    https://www.twilio.com/blog/asynchronous-javascript-advanced-promises-chaining-collections-nodejs

    View Slide

  66. Introduction to JavaScript Promises
    https://www.twilio.com/blog/asynchronous-javascript-refactor-callbacks-promises-node-js

    View Slide

  67. RxJS

    View Slide

  68. 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 Slide

  69. RxJS
    const clock$: Subject = new Subject();
    const interval = setInterval(() => {
    clock$.next('tick');
    }, 1000);
    tick; tick
    let subscription = clock$.subscribe(val => console.log(val));
    setTimeout(() => subscription.unsubscribe(), 3 * 1000);

    View Slide

  70. 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 Slide

  71. 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; tock; tick; tock
    clock$.pipe(
    map((val, index) => index % 2 == 0 ? val : 'tock')
    ).subscribe(val => console.log(val));
    import { map } from 'rxjs/operators';

    View Slide

  72. 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;
    setTimeout(() => console.log('Still alive?'), 12 * 1000);

    View Slide

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

    View Slide

  74. 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;
    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 Slide

  75. 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;
    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 Slide

  76. 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 Slide

  77. 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),
    List of directors

    View Slide

  78. 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),
    Quentin Movies

    View Slide

  79. 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 => {
    Quentin Movies

    View Slide

  80. RxJS & REST
    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 => {
    Quentin Movies

    View Slide

  81. RxJS & REST
    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; let sum = 0;
    reviews.forEach(review => {count++; sum+=review.score});
    return sum/reviews.length;
    }),
    map(average => {
    return {title: movie.title, averageScore: average};
    })
    )
    );
    });
    Quentin Movies

    View Slide

  82. RxJS & REST
    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; let sum = 0;
    reviews.forEach(review => {count++; sum+=review.score});
    return sum/reviews.length;
    }),
    map(average => {
    return {title: movie.title, averageScore: average};
    })
    )
    );
    });
    return combineLatest(observables$);
    }),
    map(movies => movies.sort((movie1, movie2) => movie2.averageScore - movie1.averageScore)),
    map(movies => movies[0].title)
    )
    [{title: movie,
    averageScore: score}]

    View Slide

  83. RxJS & REST
    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; let sum = 0;
    reviews.forEach(review => {count++; sum+=review.score});
    return sum/reviews.length;
    }),
    map(average => {
    return {title: movie.title, averageScore: average};
    })
    )
    );
    });
    return combineLatest(observables$);
    }),
    map(movies => movies.sort((movie1, movie2) => movie2.averageScore - movie1.averageScore)),
    map(movies => movies[0].title)
    )
    .subscribe(console.log);
    [{title: movie,
    averageScore: score}]

    View Slide

  84. 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 Slide

  85. 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 Slide

  86. Introducing ReactiveX and RxJS Observables
    https://www.twilio.com/blog/asynchronous-javascript-reactivex-rxjs-observables-nodejs

    View Slide

  87. Using RxJS Observables with REST APIs in Node.js
    https://www.twilio.com/blog/asynchronous-javascript-refactor-callbacks-promises-node-js

    View Slide

  88. Tracking the ISS with Real-Time Event Notifications Using Node.js, RxJS Observables,
    and Twilio Programmable SMS
    https://www.twilio.com/blog/real-time-event-notifications-using-node-js-rxjs-observables-twilio-programmable-sms

    View Slide

  89. Which Operator to Use?
    https://xgrommx.github.io/rx-book/content/which_operator_do_i_use/index.html

    View Slide

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

    View Slide

  91. Make the code synchronous again

    View Slide

  92. 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 Slide

  93. Make the code asynchronous again!
    const multiplier = getMultiplier;
    const 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 Slide

  94. 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 Slide

  95. 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 Slide

  96. 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 Slide

  97. const fetch = require('node-fetch');
    (async () => {
    })();

    View Slide

  98. 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;

    View Slide

  99. 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());

    View Slide

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

    View Slide

  101. 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 Slide

  102. 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 Slide

  103. 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 Slide

  104. Introducing async and await
    https://www.twilio.com/blog/asynchronous-javascript-introducing-async-and-await

    View Slide

  105. Using Promises With REST APIs in Node.js
    https://www.twilio.com/blog/asynchronous-javascript-using-promises-rest-apis-nodejs

    View Slide

  106. 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 Slide

  107. Feedback
    https://bit.ly/2UthmIy

    View Slide

  108. @maciejtreder

    View Slide