JavaScript este sincron. Aceasta înseamnă că va executa blocul de cod prin comandă după ridicare. Înainte de executarea codului, var și function declarațiile sunt „ridicate” până la vârful domeniului lor de aplicare.

Acesta este un exemplu de cod sincron:

console.log('1')

console.log('2')

console.log('3')

Acest cod va înregistra în mod fiabil „1 2 3”.

Solicitările asincrone vor aștepta finalizarea unui cronometru sau o solicitare de răspuns în timp ce restul codului continuă să se execute. Atunci când este momentul potrivit a suna inapoi va pune în acțiune aceste cereri asincrone.

Acesta este un exemplu de cod asincron:

console.log('1')

setTimeout(function afterTwoSeconds() {
  console.log('2')
}, 2000)

console.log('3')

Aceasta va înregistra de fapt „1 3 2”, deoarece „2” este pe un setTimeout care se va executa, doar prin acest exemplu, după două secunde. Aplicația dvs. nu se blochează în așteptarea finalizării celor două secunde. În schimb, continuă să execute restul codului și când timpul de expirare este terminat, revine la afterTwoSeconds.

Puteți întreba „De ce este util acest lucru?” sau „Cum obțin codul meu asincron pentru a deveni sincronizat?”. Sper că vă pot arăta răspunsurile.

“Problema”

Să spunem că obiectivul nostru este să căutăm un utilizator GitHub și să obținem toate depozitele acelui utilizator. Problema este că nu știm numele exact al utilizatorului. Deci, trebuie să listăm toți utilizatorii cu nume similare și depozitele lor respective.

Nu are nevoie de super-fantezie, ceva de genul asta

JavaScript de la apeluri de apel la asincronizare
Atât de mult stil, wow! Acesta este “finactiv ”

În aceste exemple, codul de solicitare va utiliza XHR (XMLHttpRequest). Îl puteți înlocui cu jQuery $.ajax sau abordarea nativă mai recentă numită fetch. Ambele îți vor oferi promisiunile în afara porții.

Acesta va fi ușor modificat în funcție de abordarea dvs., dar ca starter:

// url argument can be something like 'https://api.github.com/users/daspinola/repos'

function request(url) {
  const xhr = new XMLHttpRequest();
  xhr.timeout = 2000;
  xhr.onreadystatechange = function(e) {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
       // Code here for the server answer when successful
      } else {
       // Code here for the server answer when not successful
      }
    }
  }
  xhr.ontimeout = function () {
    // Well, it took to long do some code here to handle that
  }
  xhr.open('get', url, true)
  xhr.send();
}

Amintiți-vă că în aceste exemple partea importantă nu este rezultatul final al codului. În schimb, obiectivul dvs. ar trebui să fie să înțelegeți diferențele dintre abordări și modul în care le puteți utiliza pentru dezvoltarea dvs.

Suna inapoi

Puteți salva o referință a unei funcții într-o variabilă atunci când utilizați JavaScript. Apoi le puteți folosi ca argumente ale unei alte funcții pentru a le executa ulterior. Acesta este „callback-ul” nostru.

Un exemplu ar fi:

// Execute the function "doThis" with another function as parameter, in this case "andThenThis". doThis will execute whatever code it has and when it finishes it should have "andThenThis" being executed.

doThis(andThenThis)

// Inside of "doThis" it's referenced as "callback" which is just a variable that is holding the reference to this function

function andThenThis() {
  console.log('and then this')
}

// You can name it whatever you want, "callback" is common approach

function doThis(callback) {
  console.log('this first')
  
  // the '()' is when you are telling your code to execute the function reference else it will just log the reference
  
  callback()
}

Folosind callback pentru a rezolva problema noastră ne permite să facem așa ceva către request funcție definită mai devreme:

function request(url, callback) {
  const xhr = new XMLHttpRequest();
  xhr.timeout = 2000;
  xhr.onreadystatechange = function(e) {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
       callback(null, xhr.response)
      } else {
       callback(xhr.status, null)
      }
    }
  }
  xhr.ontimeout = function () {
   console.log('Timeout')
  }
  xhr.open('get', url, true)
  xhr.send();
}

Funcția noastră pentru solicitare va accepta acum un callback astfel încât atunci când a request se face se va apela în caz de eroare și în caz de succes.

const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`

request(userGet, function handleUsersList(error, users) {
  if (error) throw error
  const list = JSON.parse(users).items
  
  list.forEach(function(user) {
    request(user.repos_url, function handleReposList(err, repos) {
      if (err) throw err
      // Handle the repositories list here
    })
  })
})

Descompunem acest lucru:

  • Facem o cerere pentru a obține depozitele unui utilizator
  • După finalizarea solicitării, folosim apelul invers handleUsersList
  • Dacă nu există nicio eroare, analizăm răspunsul serverului nostru într-un obiect folosind JSON.parse
  • Apoi iterăm lista noastră de utilizatori, deoarece poate avea mai multe
    Pentru fiecare utilizator solicităm lista de depozite.
    Vom folosi adresa URL care a fost returnată per utilizator în primul nostru răspuns
    Noi sunam repos_urlca adresă URL pentru următoarele solicitări sau de la primul răspuns
  • Când solicitarea va finaliza apelul invers, vom apela
    Aceasta va gestiona fie eroarea sau răspunsul cu lista de depozite pentru acel utilizator

Notă: Trimiterea erorii mai întâi ca parametru este o practică obișnuită mai ales atunci când se utilizează Node.js.

O abordare mai „completă” și mai lizibilă ar fi aceea de a avea o anumită gestionare a erorilor. Vom păstra apelul invers separat de execuția cererii.

Ceva de genul:

try {
  request(userGet, handleUsersList)
} catch (e) {
  console.error('Request boom! ', e)
}

function handleUsersList(error, users) {
  if (error) throw error
  const list = JSON.parse(users).items
  
  list.forEach(function(user) {
    request(user.repos_url, handleReposList)
  })
}

function handleReposList(err, repos) {
  if (err) throw err
  
  // Handle the repositories list here
  console.log('My very few repos', repos)
}

Acest lucru sfârșește prin a avea probleme, cum ar fi curse și probleme de gestionare a erorilor. Curse se întâmplă atunci când nu controlezi ce utilizator vei primi mai întâi. Solicităm informații pentru toate în cazul în care există mai multe. Nu luăm în considerare o comandă. De exemplu, utilizatorul 10 poate fi primul și utilizatorul 2 ultimul. Avem o posibilă soluție mai târziu în articol.

Principala problemă cu apelurile de apel este că întreținerea și lizibilitatea pot deveni o durere. Este deja un fel și codul nu face nimic. Acest lucru este cunoscut sub numele de callback hell ceea ce poate fi evitat cu următoarea noastră abordare.

1611782289 130 JavaScript de la apeluri de apel la asincronizare
Imagine preluată din aici. Răspuns iad la cel mai bun nivel.

Promisiuni

Promite că vă puteți face codul mai ușor de citit. Un nou dezvoltator poate veni la baza codului și poate vedea o ordine clară de execuție a codului dvs.

Pentru a crea o promisiune puteți utiliza:

const myPromise = new Promise(function(resolve, reject) {
  
  // code here
  
  if (codeIsFine) {
    resolve('fine')
  } else {
    reject('error')
  }
  
})

myPromise
  .then(function whenOk(response) {
    console.log(response)
    return response
  })
  .catch(function notOk(err) {
    console.error(err)
  })

Să-l descompunem:

  • O promisiune este inițializată cu un function care are resolve și reject declarații
  • Asigurați-vă codul asincron în interiorul Promise funcţie
    resolve când totul se întâmplă după dorință
    In caz contrar reject
  • Când un resolve se găsește .then metoda se va executa pentru asta Promise
    Când un reject se găsește .catch va fi declanșat

Lucruri de reținut:

  • resolve și reject acceptați un singur parametru
    resolve(‘yey’, ‘works’) va trimite doar ‘yey’ către .then funcția de apel invers
  • Dacă înlănțuiți mai multe .then
    Adauga o return dacă vrei următorul .then valoare să nu fie undefined
  • Când un reject este prins cu .catch dacă aveți un .then legat de el
    Încă va executa asta .then
    Poti vedea .then ca „execută întotdeauna” și puteți verifica un exemplu în acest sens cometariu
  • Cu un lanț pe .then dacă se întâmplă o eroare pe prima
    Se va sări ulterior .then până când găsește o .catch
  • O promisiune are trei stări
    in asteptarea
  • Când așteptați o resolve sau reject a se intampla
    rezolvat
    respins
  • Odată ce se află într-un resolved sau rejected stat
    Nu poate fi schimbat

Notă: Puteți crea promisiuni fără funcția în momentul declarațiilor. Modul în care îl arăt este doar un mod obișnuit de ao face.

„Teorie, teorie, teorie … sunt confuz”, s-ar putea să spui.

Să folosim exemplul nostru de cerere cu o promisiune de a încerca să clarificăm lucrurile:

function request(url) {
  return new Promise(function (resolve, reject) {
    const xhr = new XMLHttpRequest();
    xhr.timeout = 2000;
    xhr.onreadystatechange = function(e) {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.response)
        } else {
          reject(xhr.status)
        }
      }
    }
    xhr.ontimeout = function () {
      reject('timeout')
    }
    xhr.open('get', url, true)
    xhr.send();
  })
}

În acest scenariu când executați request va returna ceva de genul acesta:

1611782289 87 JavaScript de la apeluri de apel la asincronizare
O promisiune care urmează să fie rezolvată sau respinsă
const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`

const myPromise = request(userGet)

console.log('will be pending when logged', myPromise)

myPromise
  .then(function handleUsersList(users) {
    console.log('when resolve is found it comes here with the response, in this case users ', users)
    
    const list = JSON.parse(users).items
    return Promise.all(list.map(function(user) {
      return request(user.repos_url)
    }))
  })
  .then(function handleReposList(repos) {
    console.log('All users repos in an array', repos)
  })
  .catch(function handleErrors(error) {
    console.log('when a reject is executed it will come here ignoring the then statement ', error)
  })

Așa rezolvăm cursele și unele dintre problemele de gestionare a erorilor. Codul este încă puțin complicat. Dar este un mod de a vă arăta că această abordare poate crea și probleme de lizibilitate.

O soluție rapidă ar fi separarea apelurilor inversă astfel:

const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`

const userRequest = request(userGet)

// Just by reading this part out loud you have a good idea of what the code does
userRequest
  .then(handleUsersList)
  .then(repoRequest)
  .then(handleReposList)
  .catch(handleErrors)
  
function handleUsersList(users) {
  return JSON.parse(users).items
}

function repoRequest(users) {
  return Promise.all(users.map(function(user) {
    return request(user.repos_url)
  }))
}

function handleReposList(repos) {
  console.log('All users repos in an array', repos)
}

function handleErrors(error) {
  console.error('Something went wrong ', error)
}

Privind la ce userRequest așteaptă în ordine cu .then puteți obține o idee despre ceea ce așteptăm de la acest bloc de cod. Totul este mai mult sau mai puțin separat de responsabilitate.

Aceasta „zgârie suprafața” a ceea ce sunt Promisiunile. Pentru a avea o perspectivă excelentă asupra modului în care funcționează, nu vă pot recomanda suficient acest lucru articol.

Generatoare

O altă abordare este utilizarea generatoarelor. Acest lucru este ceva mai avansat, așa că, dacă începeți, nu ezitați să treceți la următorul subiect.

O utilizare a generatoarelor este că vă permit să aveți cod asincronizat care arată ca sincronizare.

Ele sunt reprezentate de o * într-o funcție și arată ceva de genul:

function* foo() {
  yield 1
  const args = yield 2
  console.log(args)
}
var fooIterator = foo()

console.log(fooIterator.next().value) // will log 1
console.log(fooIterator.next().value) // will log 2

fooIterator.next('aParam') // will log the console.log inside the generator 'aParam'

În loc să se întoarcă cu un return, generatoarele au yield afirmație. Oprește executarea funcției până când a .next este creat pentru iterația funcției respective. Este similar cu .then promisiunea că se execută numai când se rezolvă se întoarce.

Funcția noastră de solicitare ar arăta astfel:

function request(url) {
  return function(callback) {
    const xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(e) {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          callback(null, xhr.response)
        } else {
          callback(xhr.status, null)
        }
      }
    }
    xhr.ontimeout = function () {
      console.log('timeout')
    }
    xhr.open('get', url, true)
    xhr.send()
  }
}

Vrem să avem url ca argument. Dar, în loc să executăm cererea din poartă, o dorim numai atunci când avem un apel invers pentru a gestiona răspunsul.

Al nostru generator ar fi ceva de genul:

function* list() {
  const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`
 
  const users = yield request(userGet)
  
  yield
  
  for (let i = 0; i<=users.length; i++) {
    yield request(users[i].repos_url)
  }
}

O sa:

  • Așteptați până primul request este pregatit
  • Întoarceți a function referință care așteaptă o callback pentru primul request
    Al nostru request funcția acceptă a url
    și returnează un function care așteaptă o callback
  • Asteptati-va users să fie trimise în următoarea .next
  • Iterează users
  • Așteptați un .next pentru fiecare dintre users
  • Reveniți la funcția de apelare respectivă

Deci, o execuție a acestui lucru ar fi:

try {
  const iterator = list()
  iterator.next().value(function handleUsersList(err, users) {
    if (err) throw err
    const list = JSON.parse(users).items
    
    // send the list of users for the iterator
    iterator.next(list)
    
    list.forEach(function(user) {
      iterator.next().value(function userRepos(error, repos) {
        if (error) throw repos
        
        // Handle each individual user repo here
        console.log(user, JSON.parse(repos))
      })
    })
  })  
} catch (e) {
  console.error(e)
}

Am putea separa funcțiile de apel invers așa cum am făcut anterior. Veți obține oferta până acum, o opțiune de plătit este că acum putem gestiona fiecare listă individuală de depozite de utilizatori individual.

Am tăiat mixt despre generatoare. Pe de o parte, pot să înțeleg ceea ce se așteaptă de la cod, uitându-mă la generator.

Dar execuția sa ajunge să aibă probleme similare cu infernul de apel invers.

Ca asincronizat / așteaptă, este recomandat un compilator. Acest lucru se datorează faptului că nu este acceptat în versiunile mai vechi de browser.

De asemenea, nu este atât de obișnuit în experiența mea. Deci, poate genera confuzii în bazele de cod menținute de diverși dezvoltatori.

O prezentare minunată a modului în care funcționează generatoarele poate fi găsită în acest sens articol. Și iată un alt mare resursă.

Async / Await

Această metodă pare un amestec de generatoare cu promisiuni. Trebuie doar să-i spui codului tău ce funcții trebuie să fie async. Și ce parte a codului va trebui să facă await pentru asta promise a termina.

sumTwentyAfterTwoSeconds(10)
  .then(result => console.log('after 2 seconds', result))
  
async function sumTwentyAfterTwoSeconds(value) {
  const remainder = afterTwoSeconds(20)
  return value + await remainder
}

function afterTwoSeconds(value) {
  return new Promise(resolve => {
    setTimeout(() => { resolve(value) }, 2000);
  });
}

În acest scenariu:

  • Noi avem sumTwentyAfterTwoSeconds ca fiind o funcție asincronă
  • Îi spunem codul nostru să aștepte resolve sau reject pentru funcția noastră de promisiune afterTwoSeconds
  • Va ajunge doar în .then cand await operațiile se termină
    În acest caz, există doar unul

Aplicând acest lucru la request îl lăsăm ca un promise așa cum am văzut mai devreme:

function request(url) {
  return new Promise(function(resolve, reject) {
    const xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(e) {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.response)
        } else {
          reject(xhr.status)
        }
      }
    }
    xhr.ontimeout = function () {
      reject('timeout')
    }
    xhr.open('get', url, true)
    xhr.send()
  })
}

Noi ne creăm async funcționează cu necesarul așteaptă așa:

async function list() {
  const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`
  
  const users = await request(userGet)
  const usersList = JSON.parse(users).items
  
  usersList.forEach(async function (user) {
    const repos = await request(user.repos_url)
    
    handleRepoList(user, repos)
  })
}

function handleRepoList(user, repos) {
  const userRepos = JSON.parse(repos)
  
  // Handle each individual user repo here
  
  console.log(user, userRepos)
}

Deci, acum avem o sincronizare list funcție care va gestiona solicitările. O altă sincronizare este necesară în forEach astfel încât să avem lista de repos pentru fiecare utilizator să manipuleze.

Îl numim astfel:

list()
  .catch(e => console.error(e))

Aceasta și abordarea promisiunilor sunt preferatele mele, deoarece codul este ușor de citit și de schimbat. Puteți citi despre asincronizare / așteptați mai în profunzime aici.

Un dezavantaj al utilizării async / await este că nu este acceptat în front-end de browsere mai vechi sau în back-end. Trebuie să utilizați nodul 8.

Puteți utiliza un compilator de genul babel pentru a ajuta la rezolvarea asta.

“Soluţie”

Poti vedea cod final îndeplinirea obiectivului nostru inițial folosind async / await în acest fragment.

Un lucru bun de făcut este să-l încercați singur în diferitele forme la care se face referire în acest articol.

Concluzie

În funcție de scenariul pe care l-ați putea folosi:

  • asincronizat / așteaptă
  • apeluri de apel
  • amesteca

Depinde de tine ceea ce se potrivește scopurilor tale. Și ceea ce vă permite să mențineți codul, astfel încât acesta să poată fi înțeles de către ceilalți și de sinele dvs. viitor

Notă: Oricare dintre abordări devine puțin mai detaliată atunci când se utilizează alternative pentru solicitări precum $.ajax și fetch.

Spuneți-mi ce ați face diferite și diferite modalități pe care le-ați găsit pentru a face fiecare abordare mai lizibilă.

Acesta este articolul 11 ​​din 30. Face parte dintr-un proiect pentru publicarea unui articol cel puțin o dată pe săptămână, de la gânduri inactive la tutoriale. Lasă un comentariu, urmărește-mă pe Diogo Spínola și apoi întoarce-te la genialul tău proiect!