Inlantuirea promisiunilor folosind reduce()- partea 2

Un exemplu concret cu call-uri API

Daniel Turcu

4 minute read

Inlantuirea promisiunilor folosind reduce - partea 2


Exemplu folosind request-uri API

Acest tutorial vine in continuarea articolului de aici. In prima parte am simulat request-urile asincronice cu setTimeout().

Vom modifica putin cerintele si vom apela un API real, parametrizabil. Vrem sa lansam mai multe apeluri catre acelasi API, dar cu parametri diferiti (de exemplu productId=1, productId=2, etc.). Apelurile nu le vrem concurente, ci inlantuite. Incheierea unui request, va lansa automat al doilea request dintr-o coada creata de noi (un array de promisiuni).

Vom folosi API-ul https://jsonplaceholder.typicode.com/posts?userId=1. Puteti inlocui API-ul cu orice alt API care sa permita trimiterea de parametri.


Functia de inlantuire a request-urilor

Functia de inlantuire a apelurilor (API requests) ramane la fel ca cea din primul articol.


function inlantuireApeluri(requests, input) {
    return requests.reduce(
      (lantulApelurilor, requestulCurent) => {
         return lantulApelurilor.then(requestulCurent)
      },
      Promise.resolve(input)
    )
}   

  • requests este un array de functii care returneaza promisiuni

  • aplicand metoda reduce, acumularea (lantulApelurilor) este de fapt inlantuirea rezultatelor tuturor functiilor din array (adica inlantuirea promises-urilor).

Prin inlantuirea promisiunilor, evitam lansarea acestora in paralel. Daca rezultatele de pe un request depind de rezultatul de pe alt request, prin inlantuire putem astepta ca primul request sa se termine inainte de lansarea celui de-al doilea.

 prom
    .then( function(data) { 
        console.log('rezultat prom1: ', data); 
        return { info: 'Custom data', data1: data } 
    } ) 
    .then( function(data) { console.log('folosim datele din primul request: ', data.data1); } )
    

sau varianta ES6:

 prom1
    .then( (data) => { 
        console.log('rezultat prom1: ', data); 
        return { info: 'Custom data', data1: data } 
    } ) 
    .then( (data) => console.log('folosim datele din primul request: ', data.data1) )
    

Factory

Vom crea o functie wrapper (function factory) care sa ne permita sa cream functii similare. In articolul anterior aveam functii diferite, request-uri diferite; aici avem acelasi endpoint, request-uri similare cu parametri diferiti:

GET https://jsonplaceholder.typicode.com/posts?userId=1

GET https://jsonplaceholder.typicode.com/posts?userId=2

GET https://jsonplaceholder.typicode.com/posts?userId=3

Deci, o functie generica de creare a acestor request-uri, care sa primeasca parametrul userId are sens:


function requestFactory(userId) {

    // functia asta va fi pusa in coada (functii callback pentru .then())
    return function(prevData) {    // un obiect cu rezultatele anterioare 
        return new Promise((resolve, reject) => {   // promisiunea va fi inlantuita cu `reduce`
            const url = "https://jsonplaceholder.typicode.com/posts?userId="+userId;
    
            const options = {
                headers: {
                  "Content-type": "application/json; charset=UTF-8"
                }
              };
    
            fetch(url, options)
                .then(response => response.json())  // raspunsul de la fetch este un obiect complex
                .then(data => {     // aici este rezultatul functiei json() de mai sus
                    console.log(`Aici avem datele din request-ul ${userId}...`, data );

                    // folosim Object.assign() pentru a adauga raspunsul pe obiectul cu toate raspunsurile
                    resolve( Object.assign(prevData, { [`response${userId}`]: data }) )
                });
      
        }); 
    }

}

metoda .then(), prezenta pe obiectul de tip promisiune, asteapta o functie callback. Functia callback returneaza o promisiune la randul ei, pe care putem utiliza iar .then(), si asa mai departe. Factory-ul nostru creeaza, de fapt, aceste callback-uri


Stabilirea ordinii de executie a request-urilor

Vom crea o functie care primeste un array de callback-uri (aceste functii vor fi parametri pentru .then()).

Mai sus am facut deja un factory de callback-uri, pentru ca am decis ca URL-ul pe care il apelam este acelasi, dar cu parametri diferiti.

Urmeaza sa punem fabrica de functii la lucru:


function startApeluriDinArray(requests) {
    let array = [];

    for (let req of requests) {
        console.log('Adauga call-ul cu id-ul: ' + req['id']);
        array.push( requestFactory(req['id']) );
    }

    console.log('Avem array-ul de promisiuni ce se vor lansa inlantuite: ', array);
    console.log('Asteptam datele... Putem verifica tab-ul Network sa vedem detalii despre request-uri');
    console.log('Observam in tab-ul network ca doar dupa incheierea unui request se lanseaza urmatorul');

    inlantuireApeluri(array, {})
        .then( data => {
            console.log('Datele cumulate: ', data)
        } )
        .catch( err => console.log(err) );
}

Stabilim cate request-uri vrem sa lansam si in ce ordine:


postsRequests = [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 } ];

startApeluriDinArray( postsRequests );


Mai jos avem raspunsul in consola:

Chain promises part2 - console

In consola, la final unde scrie ‘Datele cumulate', observam obiectul final care are toate raspunsurile, adica e atotstiutor… ;-) .


Daca inspectam tab-ul Network vedem inlantuirea request-urilor (nu se lanseaza concurent):

Chain promises part2 - network


Si un gif care exemplifica modul cum se lanseaza request-urile:

Chain promises part2 - network gif

comments powered by Disqus