JavaScript

Async JS & Promises

Syfte

  • Förstå vad som menas med asynkron programmering och hur det fungerar i Javascript
     
  • Det moderna sättet att hantera asynkrona processer i Javascript (och hur det var förut). Callbacks => Promises =>  async / await

Syfte

 I slutändan ska du kunna bemästra de här två sätten fär asynkron hantering.

 


const getData = function(url) {
     fetch(url)
        .then(response => response.json())
        .then(data => console.log(data))
        .catch(error => console.log(error));
}

getData(url);

Promise Chaining


const getData = function(url) {
     fetch(url)
        .then(response => response.json())
        .then(data => console.log(data))
        .catch(error => console.log(error));
}

getData(url);

Promise Chaining


const getData = async function(url) {
    try {
        const response = await fetch(url);
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.log(error);
    }
}

getData(url);

Async / Await

Synkron programmering

Varje instruktion måste avslutas innan nästa instruktion kan köras.

Fler processer kan alltså inte köras samtidigt.

const arr = [1,3,4,5,6,7]
const anotherArr = [1,3,4]

for(let i = 0; i < arr.length; i++) {
  console.log(arr[i])
}

console.log("For loop 1 done...")

for(let j = 0; j < anotherArrlength; j++) {
  console.log(anotherArr[j])
}

console.log("For loop 2 done...")

betyder "en sak i taget"

En del rutiner tar tid. Exempel HTTP-requests eller hämta från databas. Hur hantera vi detta?

Problemet

En kö av instruktioner

sånt som blockar!

Javascript är single-threaded, så vi kan inte lösa problemet med fler trådar/processer

Statement 1

Statement 2

Statement 3

Statement 4

HTTP REQUEST

🕰

HÄMTA FRÅN DB

🕰

BLOCKERANDE 👎

Problemet med synkront, där vissa processer tar tid och blockerar

När man bygger webbapplikationer som efterfrågar från API:er eller en databas blir det här problemet mer påtagligt. Ingen användare vill att ett fönster ska "frysas" medan text eller bild laddas in.

 

I synkron programmering så blockar programmet när det väntar på input från användaren, eller när programmet hämtar från API/databas eller input från användaren.

Kod som körs asynkront är något som kan påbörjas men kan avslutas senare utan att den blockerar annan kod under den tiden.
Man kan säga att den asynkrona processen ligger i "bakgrunden", tas "out of order" från annan synkron programmering.

För webbapplikationer handlar långa väntetider just om väntetid (inte beräkningstid) som hämta från API/databas. Här kommer alltså asynkron programmering i Javascript till sin stora nytta!

 

Asynkron programmering

motsats till synkron

Analogi

Koka pasta

Synkront

Asynkront

KLIENT

SERVER

HTTP

förfrågan

väntar på respons

KLIENT

SERVER

HTTP

förfrågan

Andra processer kan köras

HTTP svar

=> gör något med det

Asynkront!

Statement 1

Statement 2

Statement 3

CALLBACK

HTTP REQUEST

🕰

ICKE-BLOCKERANDE 👍 

HTTP REQUEST

Starta exekveringen nu, men avsluta senare

"BAKGRUNDEN"

Javascript är single-threaded, hur fungerar asynkront då?

Javascript kan inte köra flera trådar samtidigt (multi-threaded). Det är egentligen ett synkront single-threaded programspråk.

Men med hjälp av event-loop och callback/promises kan Javascript utföra asynkrona uppgifter.

JavaScript Runtime-miljö

Call Stack

Hur Javascriptmotorn håller reda på hur funktionen anropas. Vilka funktioner som körs och vilka funktioner som väntar på att köras o.s.v.

Callstack arbetar utifrån principen LIFO - last in first out.

När en funktion anropas kommer den att läggas till call stack, när den returnas kommer information om funktionen tas bort från call stack.

function firstFunction(){
  console.log("Hello from firstFunction");
}

function secondFunction(){
  firstFunction();
  console.log("The end from secondFunction");
}

secondFunction();

I vilken ordning kommer dessa exekveras i Call Stack?

function multiply(x, y) {
    return x * y;
}
function printSquare(x) {
    var s = multiply(x, x);
    console.log(s);
}
printSquare(5);

Call Stack enligt LIFO

Bra genomgång om event-loop i JS-motorn

Loupe eller https://www.jsv9000.app/

Du ska kunna:

  • Vad är asynkron Javascript?
  • Varför är det viktigt att kunna köra vissa processer asynkront?
  • Hur hanteras asynkron Javascript  då JS är ett single-threaded language?

Idén är att vi vill kunna sätta igång en exekvering av något nu
och sedan hantera det senare.


Detta gör att vårt UI inte blir blockerat utan användaren kan
fortfarande interagera med sidan medan andra saker sker i
bakgrunden.
 

Hur löser vi blockering?

Exempel

  • Vi har en funktion som efter godtyckligt antal sekunder skriver ut making pancakes.
  • När denna funktion är klar vill vi köra en annan funktion som skriver ut let’s eat!

- Vi vill ju äta precis när pannkakorna är klara

Med Callback

function makePancakes(callback) {
    console.log("Making pancakes...");
    setTimeout(() => {
        callback(); //
    }, 1500 );
}

makePancakes(() => {
    console.log("Let's eat");
});
                 
function doStep1(init, callback) {
  const result = init + 1;
  callback(result);
}

function doStep2(init, callback) {
  const result = init + 2;
  callback(result);
}

function doStep3(init, callback) {
  const result = init + 3;
  callback(result);
}
# Callbacks

Callbacks

function doOperation() {
  doStep1(0, (result1) => {
    doStep2(result1, (result2) => {
      doStep3(result2, (result3) => {
        console.log(`result: ${result3}`);
      });
    });
  });
}
doOperation();
# Callbacks

Nästlade callbacks

Callback hell

Callbacks var sättet att bygga upp asynkron kod med innan ES6-
Blir svårt att debugga ifall det r många beroenden. Fått synonomen Callback hell.

AJAX

Asynchronous JavaScript And XML

JSON

let ajax = new XMLHttpRequest();
ajax.open('get', url);
ajax.onreadystatechange = function() {
    if(ajax.status == 200 && ajax.readyState == 4) {
        console.log('Result: ' + ajax.responseText);
    }
}
ajax.send();

AJAX -

 

Föregångaren till Fetch API som baserade den asynkrona hanteringen med callbacks!

Asynchronous JavaScript And XML

JSON

Fetch API

  • Används för att hämta data över HTTP. Som AJAX men baserat på promises
  • Finns inbyggt i Javascript motorn (WEB API). Inget externt ramverk
  • Finns även andra motsvarigheter i form externa ramverka, exempelvis axios och superagent

Promises

ett helt klart bättre alternativ

Vad är en Promise?

Ett Promise i JS är ett objekt som kommer returnera ett värde i framtiden.

 

Tänk på det som att man gör ett löfte att göra något.


Promises är bra att använda när man inte vet hur lång tid nåt
kommer ta, t.ex. ett request till ett API på en server.

Analogi

Jobbintervju, eller LIA-intervju 😃

👤

Promise 🤞

  • Skapar ett löfte

  • Infrias / ej infrias

Jobbintervju 📞 :

"Vi hör av oss till dig om en vecka"

Ett Promise har 3 olika tillstånd

Pending

  • Initiala tillståndet är varken fullfilled eller rejected
  • Slutfördes som utlovades
  • Slutfördes inte som utlovades

Fullfilled

Rejected

Fetch()

const data = fetch(url)

Fetch() startar processen för att hämta en resurs över HTTP/JSON-fil

Returnerar en Promise

Använda fetch()

Hantering av två möjliga tillstånd


fetch(url)
        .then(response => response.json()) // Om svaret fås, konvertera svaret till JSON
        .then(data => console.log(data)) // Hantera svaret
        .catch(error => console.log(error)); // Hantera eventuella fel

Exempel: Skapa egen Promise

Kasta ett mynt - krona simulerar resolved och klave reject.

Promise

const myPromise = new Promise((resolve, reject) =>  {
  if (/* Everything works */) {
    resolve("Success. Promise was resolved");
  } else {
    reject("Fail. Promise was rejected");
  }
});

Ett Promise, rent konkret, är ett objekt som kommer returnera ett värde i framtiden.

Callbackfunktionen tar två argument, "resolve" och "reject".

Går det bra körs resolve(), annars körs reject()

Promise - then och catch

  // Promise chaining
  myPromise.then((message) => {
    console.log(message);
  }).catch((message) => {
    console.log(message);
  });

För att hantera ett Promise kan vi använda then() och catch()

then() är en funktion som används för att hantera resultatet av Promise när den har lyckats

catch() är en funktion som används för att hantera resultatet av Promise när den har misslyckats.

  fetch("https://jsonplaceholder.typicode.com/todos/")
   .then((response) => response.json())
   .then((json) => console.log(json))
   .catch((error) => console.log(error));

Exempel then/catch med fetch()

Funktionen fetch() är inbyggd i (de allra flesta) webbläsare med Fetch API. Den används för att göra en HTTP-request till en given URL och den returnerar en Promise.

I varje then() måste resultat returneras som en ny Promise så att nästa then() kan hantera det.

 catch() behöver inte returnera en ny Promise.

Throw

const promise = new Promise(function (resolve, reject) {
    if (/* Everything works */) {
      resolve("Success. Promise was resolved");
    } else if {
        if(undefined) {
            throw new Error("An error occured!")
        }
    } else {
      reject("Fail. Promise was rejected");
    }
  });

promise.catch((error) => {
  
  console.error(error); // Expected output: An error occured!!
  
});

Throw används för att skapa ett undantag. Dess undantag tas emot i catch.

Promise chaining

doSomething()
  .then(function (result) {
    return doSomethingElse(result);
  })
  .then(function (newResult) {
    return doThirdThing(newResult);
  })
  .then(function (finalResult) {
    console.log(`Got the final result: ${finalResult}`);
  })
  .catch(function(error) {
   console.log(error)
});

När man vill exekvera relatera asynkron operationer efter varandra. Nästa then() startar när den tidigare är resolved. Resultatet måste returneras för att vara åtkomlig i nästa then()

Mer om Promises

Utöver resolve, reject, then och catch, så
finns det ytterligare sätt att hantera Promises

  • Promise.all()
  • Promice.race()

Promise.all()

Promise.all() används för att invänta all data från inpassade
promises - väntar bara på att alla resolve:ar, om en ger fel så
avbryts det helt. Användbart när vi vill göra flera parallella asynkrona
anrop och få tillbaka svaren när de är klara.

Exempel Promise all

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('The first promise has resolved');
      resolve(10);
    }, 1 * 1000);
  });
  const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('The second promise has resolved');
      resolve(20);
    }, 2 * 1000);
  });
  const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('The third promise has resolved');
      resolve(30);
    }, 3 * 1000);
  });
  
  Promise.all([p1, p2, p3]).then((results) => {
    const total = results.reduce((p, c) => p + c);
  
    console.log(`Results: ${results}`);
    console.log(`Total: ${total}`);
  });
# PROMISES

Promise.race()

Om man vill ha den promise av två som resolvas snabbast kan
man använda Promise.race(), då ignoreras den långsammare resolven kan se ut såhär:

 const p1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 500, "Ettan vann!");
  });

  const p2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, "Tvåan vann!");
  });

  Promise.race([p1, p2]).then(value => {
    console.log(value);
  });

Problem med then() och promise-chaining

  • Promises kombinerat med then() och catch() är mycket bättre än att enbart callbacks men även här finns det några saker som kan förbättras.
  • Genom att skriva asynkron kod förlorar vi lättläsbarheten och debugbarheten i våra program som finns där när vi skriver sekventiellt. Därför skulle vi vilja kunna skriva asynkron kod fast lägga upp den på samma sätt som sekventiell kod.

async / await

  • Låter oss skriva asynkron kod så att den ser ut som synkron kod (d.v.s lättare att läsa)

  • async - sätts före funktionen, d.v.s vi säger att funktionen ska returnera en promise!

  • await - pausar exekveringen av den asynkrona funktionen och väntar på att Promise ska fullföljas

async / await

async function fn() {
  
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 3000);
  });
  
  let result = await promise; // wait until the promise resolves 
  
  console.log(result); // "done!"
}
fn(); //kör en funktion som tar 3 sekunder

async / await

async function fn() {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 3000);
  });
  let result = await promise; // wait until the promise resolves (*)
  console.log(result); // "done!"
}
// Kör 3 funktioner som borde ta 3 sekunder
// Nu tar alla 3 ungefär 3 sekunder
fn();
fn();
fn();

async / await

async function getData() {

   let response = await fetch(url);
   // Väntar på svar från server och 
   // lägger datat i json-format
   let data = await response.json();
   // Väntar på att json-datat görs 
   // om till ett objekt
   console.log(data); // Skriver ut vårt data
}

getData();

try & catch

  • try gör så att vi kan definiera en block som ska testas för fel/errors medan det exekveras
  • Med hjälp av catch så kan vi definiera en sektion av kod som ska utföras ifall det uppstår ett fel/error i try-blocket
  • try och catch kommer i par, det vill säga att vi kan inte bara använda den ena av dom, utan behöver initiera båda

Allmänt verktyg för felhantering

try & catch

exempel

function isValidJSON(text) {
    if (typeof text!=="string"){
        return false;
    }
    try {
        let json = JSON.parse(text);
        console.log(json)
        return (typeof json === 'object');
    }
    catch (error){
        console.log(error)
        return false;
    }
  }

 isValidJSON(input)

Async/Await!

async function getData() {

 try { //Om promise inte blir "resolved" => gå till catch
 
   let response = await fetch(url);

   let data = await response.json();

   console.log(data); 
   
   } catch(err) { // promise "rejected"
     console.log(err);
   }
   
}

getData();

hantera resolved + reject med async funktion

Async/Await!


async function getData() {

    try { // Om ej lyckas, d.v.s ingen 
          // promise resolved => gå till catch
      let response = await fetch(url);
   
      let data = await response.json();

      return data;
      
      } catch(err) { // promise rejected
        console.log(err);
      }
    
   }
   
  async function renderData() {
    const data = await getData();
    console.log(data);
}

renderData();

returnera från en async funktion

Async/Await!

async function getData() {
  try {
    // Om ej lyckas, d.v.s ingen
    // promise resolved => gå till catch
    let response = await fetch(`http://jsonplaceholder.typicode.com/comments`);

    let data = await response.json();

    return data;
  } catch (err) {
    // promise rejected
    console.log(err);
  }
}

 getData().then(data => { 
       console.log(data); 
   });

Returna från async/await 

Historik

  1. callbacks
  2. promises
  3. async/await