Daniel Turcu

4 minute read

Inlantuirea promisiunilor folosind reduce

Metoda reduce() (Array.prototype.reduce) ne poate ajuta sa cream un flux bine definit de executie al promisiunilor. Cateodata ar fi util sa putem controla ordinea de executie a unor apeluri asincronice.

Sa presupunem ca, din diverse motive, trebuie sa executam trei request-uri inlantuite catre un server API. Daca nu avem dependente intre request-uri sau restrictii ale serverului de API, pot fi executate in paralel.

Doua motive de inlantuire ar fi: dependenta intre request-uri, de exemplu al doilea request nu poate fi lansat fara datele returnate de la primul request, sau limitari din partea serverului pentru a evita supraincarcarea.

Avem urmatorul scenariu:

  • 3 request-uri http catre un server API (vom simula asta cu functia setTimeout)
  • un calcul local (o functie normala care nu are nimic asincronic in ea (un simplu calcul) )
  • vrem sa interpunem calculul intre al doilea si al treilea request
    • deci, scenariul presupune ca requestul trei are nevoie si de datele puse la dispozitie de functie

Incepem prin definirea functiilor care simuleaza requesturile catre server:


// apelul 1 catre server
function req1(data) {

  return new Promise((resolve, reject) => {
    setTimeout(()=>{
         // aici consideram ca avem response-ul de pe server
        //  putem folosi datele din apelul asincronic (de ex API request) 
         let responseData = [1,2,3];
         console.log(responseData, data);
        // aici luam decizia de a atasa raspunsul pe un obiect 
        // impreuna cu datele din request-ul anterior (daca exista)
         resolve( Object.assign(data, { firstReq: responseData }) )      
    }, 1000)
    
  })

}

// apelul 2 catre server
function req2(data) {

  return new Promise((resolve, reject) => {
    setTimeout(()=>{
         let responseData = [10,20,30];
         console.log(responseData, data);
        //  la al doilea request din lant, data arata asa: { firstReq: [10,20,30] }
        // urmeaza sa atasam si al doilea raspuns: { firstReq: [10,20,30], secondReq: [10,20,30] }
         resolve( Object.assign(data, { secondReq: responseData }) )        
    }, 1500)
  })

}

// apelul 3 catre server
function req3(data) {
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
         let responseData = [1000,2000,3000,4000];
         console.log(responseData, data);
         resolve( Object.assign(data, { thirdReq: responseData }) )      
    }, 2000)
    
  })
}

// orice functie (local) 
// nu e promisiune dar va fi transformata intr-una la inlantuire 
function oriceCalcul(data) {
  return Object.assign(data, { localData: [100,200] });   
}

Urmeaza sa cream o functie care se va ocupa de inlantuirea functiilor, fie ca returneaza o promisiune sau o valoare finala:



function inlantuireApeluri(requests, initData) {
  return requests.reduce(
    (lantulApelurilor, functiaCurenta) => {
        // am pornit la drum cu promisiunea wrapper, avem metoda .then()
       return lantulApelurilor.then(functiaCurenta)     
    },
    Promise.resolve(initData)   // cream un wrapper peste datele initiale
  )
}

Observam ca, desi am denumit functia inlantuireApeluri, nu ne referim doar la functii care fac request-uri http ci si la posibilitatea de a inlantui orice functie, chiar si cu cod sincronic.

Mai jos vom finaliza codul prin crearea unui array de functii. Ordinea in care le punem in array va fi ordinea de inlantuire.


const requestsArr = [req1, req2, oriceCalcul, req3]

// pornim la drum cu un obiect gol pe care se vor atasa rezultatele
inlantuireApeluri(requestsArr, {})
  .then( (data) => {
      console.log('Seturile de date finale: ', data);
  } )   

// firul principal continua mai departe  
console.log('FIRUL PRINCIPAL');

Daca verificam acum codul si rezultatul in consola, observam ca la fiecare nivel putem sa utilizam datele din request-urile anterioare, sau le putem doar atasa pe obiectul final.

// raspunsul in consola are forma
// [ datele din requestul curent ] { obiectul data cu datele anterioare }

[1, 2, 3] 
{}

[10, 20, 30] 
{ firstReq: [1, 2, 3] }

[1000, 2000, 3000, 4000] 
{firstReq: [1, 2, 3], secondReq: [10, 20, 30], localData: [100, 200]}


// Seturile de date finale:  
{
    firstReq: [1, 2, 3], 
    secondReq: [10, 20, 30], 
    localData: [100, 200], 
    thirdReq: [1000, 2000, 3000, 4000]
}

Chiar daca am avea un array de functii care nu executa niciun request asincronic, solutia de mai sus ne poate ajuta sa cumulam automat seturi de date/raspunsuri/calcule pe acelasi obiect. Tot ce trebuie sa facem este sa le punem intr-un array si sa apelam functia inlantuireApeluri:


function calcul1(data) {
  return Object.assign(data, { calcul1: 500 });   
}

function calcul2(data) {
  return Object.assign(data, { calcul2: [1,5,10] });   
}

const calcule = [calcul1, calcul2];

// pornim la drum cu un obiect gol pe care se vor atasa rezultatele
inlantuireApeluri(calcule, {})
  .then( (data) => {
      console.log('Seturile de date finale: ', data);
  } )   

Raspunsul in consola este:


// Seturile de date finale:  
{ 
    calcul1: 500, 
    calcul2: [1, 5, 10] 
}

Vezi si partea a doua, unde abordam o varianta putin modificata si apeluri reale catre API.

comments powered by Disqus