Promises vs Observables pro migraci z AngularJS na Angular

AngularJS (Angular 1) hojně používal Promises pro HTTP volání, zatímco Angular má síťová volání zabalená do Observables. To způsobilo, že se někteří vývojáři setkali se specifickými problémy při migraci projektů z AngularJS na Angular. Chci se zde těmito problémy zabývat a popsat, proč se vůbec mohou objevit, a to přezkoumáním typických rozdílů mezi Observables a Promises .


Observables — více možností pro webové vývojáře. (obrázek od mediamodifier)

Observables and Promises — krátký úvod

Na první pohled — Pozorovatelné jsou jen pokročilé Sliby:Sliby vydávají jednu hodnotu a jsou kompletní (vyřešit), Pozorovatelné vydávají 0, jednu nebo více hodnot a také kompletní (vysílat a dokončit jsou různé akce). Pro službu HTTP v AngularJS a Angular poskytuje pouze jednu hodnotu — takže se zdá, že oba frameworky v tomto případě fungují velmi podobně.

// Observables in Angular 2+
const sourse$ = this.httpServie.get('https://some_url.com')
source$.subscribe(
    (data) => handelData(data), // success handler
    (err) => handleError(err),  // error handler
    () => completeHandler() // onComplete handler
)

// Promises in AngularJS
const soursePromise = http$.get('https://some_url.com')
soursePromise.then(
    (data) => handelResolve(data), // resolve handler
    (err) => handleReject(err) // reject handler
)

A někdo si může myslet, že stačí přejmenovat $http na this.httpService , pak k přihlášení k odběru a všichni budou šťastní. Ve velmi jednoduchých aplikacích to dokonce může fungovat — ale pokud vaše aplikace dělá něco víc než ‚Ahoj světe‘ — věnujte pozornost těmto rozdílům.

č. 1 Dychtivý versus líný

Podívejte se na příklad níže:

//Promise-wrapped http request
saveChanges(data) {
  return $http.post('https://some_url.com', data)
}


//Observable-wrapped http request
saveChanges(data) {
  return this.httpService.post('https://some_url.com', data) // doesn't do request!
}

Když zavolám saveChanges metoda — první příklad s požadavkem zabaleným do Promise bude fungovat podle očekávání. Ale během několika sekund se nic nestane, protože pozorovatelné jsou líně vyhodnoceny zatímco Promises jsou netrpělivě vyhodnocovány.

To znamená, že Promises se nestará o to, zda mají nějaké odběratele, aby získali svůj výsledek, nebo ne. Ale Observables (abych byl přesný — studený Observable) bude studený, jen když si je předplatíme. Ve výše uvedeném případě byste se měli přihlásit k odběru Observable vráceného saveChanges funkce.

saveChanges(data).subscribe()

Chcete-li to mít na očích — použijte rxjs-no-ignored-observable pravidlo z rxjs-tslint-rules od Nicholase Jamiesona.

#2 Sliby nelze zrušit, zatímco odběr Observables lze odhlásit

Opět začněte příkladem, kdy při změně vstupního textu hledáme na back-endu:

// html
<input ngKeyup="onKeyUp($event)">

//Promise-wrapped HTTP request
saveChanges(event) {
  const text = event.target.value;
  $http.get('https://some_url.com?search=' + text)
    .then((searchResult) => showSearchResult(searchResult))
}

Co je zde nevýhodou — že nemůžete odmítnout výsledky předchozího požadavku, pokud uživatel pokračuje v psaní (debounce tento problém trochu zmenší, ale neodstraní ho). A ještě jeden problém — podmínka závodu je možná (když se pozdější výsledek požadavku vrátí rychleji než dřívější — takže se zobrazí nesprávná odpověď).

Observable se tomuto problému může celkem elegantně vyhnout pomocí switchMap operátor:

// html template
<input id="search">

//Observable-wrapped HTTP request
inputElem = document.querySelector('#search');
search$ = fromEvent(inputElem, 'keyup');

ngOnInit() {

  search$.pipe( // each time new text value is emitted
    switchMap((event) => { // switchMap cancel previous request and send a new one
      const text = event.target.value;
      return this.httpService.get('https://some_url.com?search=' + text);
    })
  )
    .subscribe((newData) => this.applyNewData(newData))  // use new data
}

Zde převádíme psaní vstupního textu na pozorovatelné hodnoty emisí. Pokaždé, když je vydána nová textová hodnota, operátor switchMap zruší předchozí síťový požadavek (pokud ještě není dokončen) a pošle nový.

Packtpub.com a já jsme připravili celek Kurz RxJS s mnoha dalšími podrobnostmi o tom, jak můžete vyřešit své každodenní úkoly vývojáře pomocí této úžasné knihovny. Může být zajímavý pro začátečníky, ale obsahuje i pokročilá témata. Podívejte se!

#3 Žádná vestavěná logika opakování nebo opakování pro Promises. „opakovat ' a zkusit znovu ' operátory pro Observables.

Logiku opakování můžete implementovat pomocí Promises, ale vypadá to trochu těžkopádně:

var request = function() {
  $http({method: 'GET', url: path})
    .success(function(response) {
      results.resolve(response)
    })
    .error(function() {
      if (counter < MAX_REQUESTS) {
        request();
        counter++;
      } else {
        results.reject("Could not load after multiple tries");
      }
    });
};

request();

Zatímco stejný kód Observables bude mnohem kratší:

this.httpService.get('https://some_url.com/data').pipe(
    retry(MAX_REQUESTS)
)

Přečtěte si více o případech použití operátorů opakování a opakování v mém článku.

#4 Malý počet kombinovaných nástrojů Promises. Observables k tomu poskytují širokou škálu operátorů.

Pro Promises jsou všechny možnosti, které můžete kombinovat, výsledky:

Promise.all — čekání na vyřešení všech slibů a poté poskytnutí řady výsledků.

Promise.race — počkejte, až bude vyřešen jeden ze slibů, a vraťte výsledek.

Pozorovatelny poskytují velmi bohatou munici pro vytváření kombinací:

  • kombinovat nejnovější(pozorovatelný1, pozorovatelný2,…) — čeká na vyslání některého z pozorovatelných a poskytne pole naposledy emitovaných hodnot ze všech pozorovatelných (výsledek:[value_obs1, value_obs2,..]). Velmi dobré, pokud byste měli aktualizovat stránku o nová data z několika různých zdrojů.

  • observable1.pipe(withLatestFrom (pozorovatelné2)  — u každé hodnoty z pozorovatelného1 také uveďte poslední emitovanou hodnotu pro pozorovatelný2 (výsledek:[value_obs1, value_obs2]).

  • forkJoin(observable1, pozorovatelný2,…) — analog for Promise.all — čeká, dokud nejsou všechny pozorovatelné položky kompletní, a poté vyšle pole posledních hodnot ze všech pozorovatelných argumentů.

  • zip (pozorovatelný1, pozorovatelný2,…) — čeká, až všechny pozorovatelné argumenty vygenerují hodnoty se stejným indexem a poskytnou pole emitovaných hodnot se stejným indexem (výsledek:[value_obs1, value_obs2,..]).

  • rasa(pozorovatelný1, pozorovatelný2,…) —  vrátí Observable, která zrcadlí první zdrojový Observable pro vysílání položky.

  • sloučit(pozorovatelný1, pozorovatelný2,…) —  přihlásí se ke každému pozorovatelnému argumentu a ze všech znovu vyšle hodnoty.

  • přepnout vše — pokud předchozí Pozorovatelný není dokončen — zrušte jej a přihlaste se k odběru nového.

  • concat( pozorovatelný1, pozorovatelný2,…) — začít další pozorovatelnou sekvenci až po dokončení předchozí (vysílá hodnoty jednu po druhé po každém konkrétním dokončení pozorovatelné)

A mnoho dalších (switchMap, mergeMap, partition, iif, groupBy, window, atd.)

Více o těchto operátorech se můžete dozvědět zde:

  1. Naučte se kombinovat sekvence RxJs se super intuitivními interaktivními diagramy
  2. Oficiální dokumenty s příklady
  3. Videokurz „Hands-on RxJS pro vývoj webu“.

#5 Snadno předcházet závodním podmínkám pomocí Observables a těžké - s Promises.

Řekněme, že pravidelně žádáme síť o aktualizovaná data. Ale v některých situacích se pozdější výsledek požadavku vrátí rychleji než předchozí — takže se nám zobrazí nesprávná (dřívější) odpověď jako poslední.

getData() {
  $http.get('https://some_url.com/data')
    .then((searchResult) => {
        doSomething(searchResult)
    }
  })
}

setTimeout(getData, 5000);

Tento kód může být pravděpodobně ovlivněn problémem sporu.

Abychom tomu zabránili u požadavků zabalených do Observable, můžeme použít operátor concatMap.

interval(5000).pipe(
    concatMap(() => this.httpService.get('https://some_url.com/data'))
)
.subscribe(doSomethingWithData)

concatMap provede další síťové volání pouze po provedení a zpracování předchozího. Samozřejmě, pokud nepotřebujete předchozí výsledky — použijte switchMap (jako v prvním příkladu tohoto článku).

Závěr

Během migrace z AngularJS (používá sliby pro síťová volání) na Angular (používá Observable) byste si měli být vědomi možných rozdílů mezi Promises a Observable. Doufám, že vám můj článek pomohl k objasnění tohoto tématu. Nyní je čas na migraci!

Líbí se vám tento článek? Zůstaňme v kontaktu na Twitteru.

Tento příspěvek byl původně publikován v ITNEXT.

Počínaje částí 4 mého Videokurz RxJS Pokročilý personál je zkontrolován — takže pokud již znáte RxJS — můžete najít něco užitečného i pro vás:pozorovatelné objekty vyššího řádu, anti-vzory, plánovače, testování jednotek atd.! Vyzkoušejte to !