Introducere

În acest articol, veți învăța cum să scrieți propria funcție promisivă de la zero.

Promisiunea ajută la gestionarea API-urilor bazate pe apelare, păstrând în același timp codul în concordanță cu promisiunile.

Am putea încheia orice funcție cu new Promise() și nu vă faceți griji deloc. Dar a face asta atunci când avem multe funcții ar fi redundant.

Dacă înțelegeți promisiunile și apelurile de apel, atunci să învățați cum să scrieți funcții promisive ar trebui să fie ușor. Asadar, haideti sa începem.

Dar v-ați întrebat vreodată cum funcționează promisiunea?

Important este să nu încetezi interogarea. Curiozitatea are propriul motiv de existență.

– Albert Einstein

Promisiunile au fost introduse în ECMA-262 Standard, ediția a 6-a (ES6) care a fost publicat în iunie 2015.

A fost destul de o îmbunătățire față de callback-uri, deoarece știm cu toții cât de ilizibil poate fi „callback hell” 🙂

Cum sa scrieti propria functie de promisiune de la zero

Ca dezvoltator Node.js, ar trebui să știți ce este o promisiune și cum funcționează intern, care vă va ajuta și în interviurile JS. Nu ezitați să le examinați rapid înainte de a citi mai departe.

De ce trebuie să convertim apelurile invers în promisiuni?

  1. Cu apeluri de apel, dacă doriți să faceți ceva secvențial, va trebui să specificați un err argument în fiecare apel invers, care este redundant. În promisiuni sau async-await, puteți adăuga doar un .catch metodă sau bloc care va surprinde orice erori apărute în lanțul promisiunilor
  2. Cu apelurile de apel, nu aveți control asupra momentului în care este apelat, în ce context sau de câte ori este apelat, ceea ce poate duce la scurgeri de memorie.
  3. Folosind promisiunile, controlăm acești factori (în special tratarea erorilor), astfel încât codul să fie mai lizibil și mai ușor de întreținut.

Cum se face ca funcțiile bazate pe apelare să returneze o promisiune

Există două moduri de a face acest lucru:

  1. Înfășurați funcția într-o altă funcție care returnează o promisiune. Apoi rezolvă sau respinge pe baza argumentelor de apel invers.
  2. Promisiune – Creăm o funcție util / helper promisify care va transforma toate API-urile bazate pe callback.

Exemplu: există un API bazat pe apelare care oferă suma a două numere. Vrem să o promitem astfel încât să returneze un thenable promisiune.

const getSumAsync = (num1, num2, callback) => {
 
  if (!num1 || !num2) {
    return callback(new Error("Missing arguments"), null);
  }
  return callback(null, num1 + num2);
}
getSumAsync(1, 1, (err, result) => {
  if (err){
    doSomethingWithError(err)
  }else {
    console.log(result) // 2
  }
})

Înfășurați-vă într-o promisiune

După cum puteți vedea, getSumPromise deleagă toată lucrarea funcției originale getSumAsync, oferind propriul apel invers care se traduce prin promisiune resolve/reject.

Promite

Când trebuie să promitem multe funcții, putem crea o funcție de ajutor promisify.

Ce este Promisiunea?

Promisiunea înseamnă transformare. Este o conversie a unei funcții care acceptă un apel invers într-o funcție care returnează o promisiune.

Folosind Node.js util.promisify():

const { promisify } = require('util')
const getSumPromise = promisify(getSumAsync) // step 1
getSumPromise(1, 1) // step 2
.then(result => {
  console.log(result)
})
.catch(err =>{
  doSomethingWithError(err);
})

Deci, pare o funcție magică care se transformă getSumAsync în getSumPromise care are .then și .catch metode

Să scriem propria noastră funcție promisivă:

Dacă te uiți la pasul 1 în codul de mai sus, promisify funcția acceptă o funcție ca argument, deci primul lucru pe care trebuie să-l facem este să scriem o funcție care poate face același lucru:

const getSumPromise = myPromisify(getSumAsync)
const myPromisify = (fn) => {}

După care, getSumPromise(1, 1) este un apel funcțional. Aceasta înseamnă că promisiunea noastră ar trebui să returneze o altă funcție care poate fi apelată cu aceleași argumente ale funcției originale:

const myPromisify = (fn) => {
 return (...args) => {
 }
}

În codul de mai sus puteți vedea că suntem răspândirea argumente pentru că nu știm câte argumente are funcția originală. args va fi un tablou care conține toate argumentele.

Când suni getSumPromise(1, 1) sună de fapt (...args)=> {}. În implementarea de mai sus, returnează o promisiune. De aceea, puteți folosi getSumPromise(1, 1).then(..).catch(..).

Sper că ați obținut indiciul că funcția wrapper (...args) => {} ar trebui să returneze o promisiune.

Întoarceți o promisiune

const myPromisify = (fn) => {
  return (...args) => {
    return new Promise((resolve, reject) => {
      
    })
  }
}

Acum partea dificilă este cum să decidem când resolve or reject o promisiune.
De fapt, acest lucru va fi decis de original getSumAsync implementarea funcției – va apela funcția originală de apel invers și trebuie doar să o definim. Apoi pe baza err și result noi vom reject sau resolve promisiunea.

const myPromisify = (fn) => {
  return (...args) => {
    return new Promise((resolve, reject) => {
      function customCallback(err, result) {
       if (err) {
         reject(err)
       }else {
         resolve(result);
        }
      }
   })
  }
}

Al nostru args[] constă doar din argumente trecute getSumPromise(1, 1) cu excepția funcției de apel invers. Deci, trebuie să adăugați customCallback(err, result) la args[]care funcția originală getSumAsync vom apela în consecință pe măsură ce urmărim rezultatul customCallback.

Apăsați CustomCallback la argumente[]

const myPromisify = (fn) => {
   return (...args) => {
     return new Promise((resolve, reject) => {
       function customCallback(err, result) {
         if (err) {
           reject(err)
         }else {
          resolve(result);
         }
        }
        args.push(customCallback)
        fn.call(this, ...args)
      })
  }
}

După cum puteți vedea, am adăugat fn.call(this, args), care va numi funcția originală în același context cu argumentele getSumAsync(1, 1, customCallback). Atunci funcția noastră promisivă ar trebui să poată resolve/reject în consecinţă.

Implementarea de mai sus va funcționa atunci când funcția originală așteaptă un apel invers cu două argumente, (err, result). Asta întâlnim cel mai des. Apoi, apelul nostru personalizat este exact în formatul potrivit și promisify funcționează excelent pentru un astfel de caz.

Dar dacă originalul fn așteaptă un apel invers cu mai multe argumente ca callback(err, result1, result2, ...)?

Pentru a o face compatibilă cu aceasta, trebuie să le modificăm myPromisify funcție care va fi o versiune avansată.

const myPromisify = (fn) => {
   return (...args) => {
     return new Promise((resolve, reject) => {
       function customCallback(err, ...results) {
         if (err) {
           return reject(err)
         }
         return resolve(results.length === 1 ? results[0] : results) 
        }
        args.push(customCallback)
        fn.call(this, ...args)
      })
   }
}

Exemplu:

const getSumAsync = (num1, num2, callback) => {
 
  if (!num1 || !num2) {
    return callback(new Error("Missing dependencies"), null);
  }
  
  const sum = num1 + num2;
  const message = `Sum is ${sum}`
  return callback(null, sum, message);
}
const getSumPromise = myPromisify(getSumAsync)
getSumPromise(2, 3).then(arrayOfResults) // [6, 'Sum is 6']

Asta e tot! Vă mulțumim că ați ajuns până aici!

Sper că vei reuși să înțelegi conceptul. Încercați să o recitiți din nou. Este un pic de cod pentru a vă înfășura capul, dar nu prea complex. Spuneți-mi dacă a fost de ajutor?

Nu uitați să îl împărtășiți cu prietenii dvs. care încep cu Node.js sau trebuie să-și ridice nivelul abilităților Node.js.

Referințe:

https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original

https://github.com/digitaldesignlabs/es6-promisify

Puteți citi alte articole de genul acesta la 101node.io.