Una dintre cele mai importante întrebări cu care m-am confruntat în interviuri a fost modul în care sunt puse în aplicare promisiunile. Deoarece async / await devine din ce în ce mai popular, trebuie să înțelegeți promisiunile.
Conţinut
Ce este o promisiune?
O promisiune este un obiect care reprezintă rezultatul unei operații asincrone care este fie rezolvată, fie respinsă (cu un motiv).
Există 3 stări
-
Îndeplinit:
onFulfilled()
va fi chemat (de exemplu,resolve()
a fost chemat) -
Respins:
onRejected()
va fi chemat (de exemplu,reject()
a fost chemat) - In asteptarea: încă neîmplinit sau respins
Deci, să vedem cum este implementat:
https://github.com/then/promise/blob/master/src/core.js
Conform definiției de la Mozilla: Este nevoie de un executor testamentar funcționează ca argument.
function noop() {}
function Promise(executor) {
if (typeof this !== 'object') {
throw new TypeError('Promises must be constructed via new');
}
if (typeof executor !== 'function') {
throw new TypeError('Promise constructor's argument is not a function');
}
this._deferredState = 0;
this._state = 0;
this._value = null;
this._deferreds = null;
if (executor === noop) return;
doResolve(executor, this);
}
Pare o funcție simplă cu unele proprietăți inițializate la 0
sau null
. Iată câteva lucruri de observat:
this._state
proprietatea poate avea trei valori posibile așa cum este descris mai sus:
0 - pending
1 - fulfilled with _value
2 - rejected with _value
3 - adopted the state of another promise, _value
Valoarea sa este0
(in asteptarea) când creați un nou promisiune.
Mai tarziu doResolve(executor, this)
este invocat cu executor and promise
obiect.
Să trecem la definiția lui doResolve
și vedeți cum este implementat.
/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*/
function doResolve(fn, promise) {
var done = false;
var resolveCallback = function(value) {
if (done) return;
done = true;
resolve(promise, value);
};
var rejectCallback = function(reason) {
if (done) return;
done = true;
reject(promise, reason);
};
var res = tryCallTwo(fn, resolveCallback, rejectCallback);
if (!done && res === IS_ERROR) {
done = true;
reject(promise, LAST_ERROR);
}
}
Aici sună din nou tryCallTwo
funcție cu executor și 2 apeluri de apel. Apelurile din nou sună resolve
și reject
done
variabila este utilizată aici pentru a vă asigura că promisiunea este rezolvată sau respinsă o singură dată, deci dacă încercați să respingeți sau să rezolvați o promisiune de mai multe ori, atunci aceasta va reveni deoarece done = true
.
function tryCallTwo(fn, a, b) {
try {
fn(a, b);
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
Această funcție apelează indirect principalul executor
callback cu 2 argumente. Aceste argumente conțin logică despre cum resolve
sau reject
ar trebui chemat. Puteți verifica resolveCallback și respinge apelare în doResolve
funcția de mai sus.
Dacă există o eroare în timpul execuției, aceasta va stoca eroarea în LAST_ERROR
și returnează eroarea.
Înainte de a sări la resolve
definiția funcției, să verificăm .then
funcția mai întâi:
Promise.prototype.then = function(onFulfilled, onRejected) {
if (this.constructor !== Promise) {
return safeThen(this, onFulfilled, onRejected);
}
var res = new Promise(noop);
handle(this, new Handler(onFulfilled, onRejected, res));
return res;
};
function Handler(onFulfilled, onRejected, promise) {
this.onFulfilled = typeof onFulfilled === "function" ? onFulfilled : null;
this.onRejected = typeof onRejected === "function" ? onRejected : null;
this.promise = promise;
}
Deci, în funcția de mai sus, atunci se creează noi promise
și atribuirea acesteia ca proprietate unei noi funcții numite Handler
. Handler
funcția are argumente onCompletat și onRefuzat. Mai târziu va folosi această promisiune pentru a rezolva sau respinge cu valoare / motiv.
După cum puteți vedea, .then
funcția apelează din nou o altă funcție:
handle(this, new Handler(onFulfilled, onRejected, res));
Implementare:
function handle(self, deferred) {
while (self._state === 3) {
self = self._value;
}
if (Promise._onHandle) {
Promise._onHandle(self);
}
if (self._state === 0) {
if (self._deferredState === 0) {
self._deferredState = 1;
self._deferreds = deferred;
return;
}
if (self._deferredState === 1) {
self._deferredState = 2;
self._deferreds = [self._deferreds, deferred];
return;
}
self._deferreds.push(deferred);
return;
}
handleResolved(self, deferred);
}
- Există o buclă de timp care va continua să atribuie obiectul promisiunii rezolvate promisiunii curente, care este, de asemenea, o promisiune pentru
_state === 3
- Dacă
_state = 0(pending)
și starea promisiunii a fost amânată până când o altă promisiune imbricată este rezolvată, apelul de apel este stocat înself._deferreds
function handleResolved(self, deferred) {
asap(function() { // asap is external lib used to execute cb immediately
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
if (self._state === 1) {
resolve(deferred.promise, self._value);
} else {
reject(deferred.promise, self._value);
}
return;
}
var ret = tryCallOne(cb, self._value);
if (ret === IS_ERROR) {
reject(deferred.promise, LAST_ERROR);
} else {
resolve(deferred.promise, ret);
}
});
}
Ce se întâmplă:
- Dacă statul este 1
(fulfilled)
apoi sunați la rezolva altceva respinge - Dacă
onFulfilled
sauonRejected
estenull
sau dacă am folosit un gol.then()
rezolvat sau respinge se va numi respectiv - Dacă
cb
nu este gol, atunci apelează o altă funcțietryCallOne(cb, self._value)
function tryCallOne(fn, a) {
try {
return fn(a);
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
} a) {
tryCallOne
: Această funcție apelează numai apelul invers care este trecut în argument self._value
. Dacă nu există nicio eroare, aceasta va rezolva promisiunea, altfel o va respinge.
Fiecare promisiune trebuie să furnizeze o .then()
metoda cu următoarea semnătură:
promise.then(
onFulfilled?: Function,
onRejected?: Function
) => Promise
- Ambii
onFulfilled()
șionRejected()
sunt opționale. - Dacă argumentele furnizate nu sunt funcții, acestea trebuie ignorate.
-
onFulfilled()
va fi chemat după ce promisiunea este îndeplinită, cu valoarea promisiunii ca prim argument. -
onRejected()
va fi chemat după ce promisiunea este respinsă, motivul respingerii fiind primul argument. - Nici
onFulfilled()
nicionRejected()
poate fi apelat de mai multe ori. -
.then()
poate fi chemat de multe ori pe aceeași promisiune. Cu alte cuvinte, o promisiune poate fi utilizată pentru a agrega apelurile de apel. -
.then()
trebuie să returneze o nouă promisiune.
Promisiunea înlănțuirii
.then
ar trebui să returneze o promisiune. De aceea putem crea un lanț de promisiuni de genul acesta:
Promise
.then(() =>
Promise.then(() =>
Promise.then(result => result)
)).catch(err)
Rezolvarea unei promisiuni
Să vedem resolve
definiția funcției pe care am lăsat-o mai devreme înainte de a trece la .then()
:
function resolve(self, newValue) {
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
if (newValue === self) {
return reject(
self,
new TypeError("A promise cannot be resolved with itself.")
);
}
if (
newValue &&
(typeof newValue === "object" || typeof newValue === "function")
) {
var then = getThen(newValue);
if (then === IS_ERROR) {
return reject(self, LAST_ERROR);
}
if (then === self.then && newValue instanceof Promise) {
self._state = 3;
self._value = newValue;
finale(self);
return;
} else if (typeof then === "function") {
doResolve(then.bind(newValue), self);
return;
}
}
self._state = 1;
self._value = newValue;
finale(self);
}
- Verificăm dacă rezultatul este sau nu o promisiune. Dacă este o funcție, atunci apelați acea funcție cu valoare folosind
doResolve()
. - Dacă rezultatul este o promisiune, atunci acesta va fi împins spre
deferreds
matrice. Puteți găsi această logică înfinale
funcţie.
Respingerea unei promisiuni:
Promise.prototype['catch'] = function (onRejected) {
return this.then(null, onRejected);
};
Funcția de mai sus poate fi găsită în ./es6-extensions.js
.
Ori de câte ori respingem o promisiune, .catch
callback este numit, care este un strat de zahăr pentru then(null, onRejected)
.
Iată diagrama brută de bază pe care am creat-o, care este o vedere de păsări a ceea ce se întâmplă în interior:
Să vedem încă o dată cum funcționează totul:
De exemplu, avem această promisiune:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Time is out");
}, 3000)
})
.then(console.log.bind(null, 'Promise is fulfilled'))
.catch(console.error.bind(null, 'Something bad happened: '))
- Promisiune
constructor
se apelează și se creează o instanță cunew Promise
-
executor
funcția este transmisă ladoResolve(executor, this)
și callback unde am definitsetTimeout
va fi chemat detryCallTwo(executor, resolveCallback, rejectCallback)
deci va dura 3 secunde pentru a termina - Sunăm
.then()
peste instanța promisiunii așa în fața noastrătimeout
este finalizat sau orice asincronizareapi
se intoarce,Promise.prototype.then
va fi numit ca.then(cb, null)
-
.then
creează un noupromise
și îl transmite ca argument cătrenew Handler(onFulfilled, onRejected, promise)
-
handle
funcția se numește cu originalulpromise
instanță șihandler
exemplu pe care l-am creat la punctul 4. - În interiorul
handle
funcție, curentself._state = 0
șiself._deferredState = 0
asa deself_deferredState
va deveni1
șihandler
instanța va fi atribuităself.deferreds
după acel control se va întoarce de acolo - După
.then()
noi sunăm.catch()
care va apela intern.then(null, errorCallback)
– din nou se repetă aceiași pași de la punctul 4 până la punctul 6 și săriți peste punctul 7 de când am sunat.catch
o singura data - Actual
promise
starea este in asteptarea și va aștepta până când va fi rezolvat sau respins. Deci, în acest exemplu, după 3 secunde,setTimeout
se apelează callback și rezolvăm în mod explicit acest lucru, care va apelaresolve(value)
. -
resolveCallback
va fi apelat cu valoareTime is out
🙂 și va apela principalulresolve
funcție care va verifica dacăvalue !== null && value == 'object' && value === 'function'
- Va eșua în cazul nostru de când am trecut
string
șiself._state
va deveni1
cuself._value="Time is out"
Și mai târziufinale(self)
se numește. -
finale
va apelahandle(self, self.deferreds)
o dată pentru căself._deferredState = 1
, și pentru lanțul promisiunilor, va apelahandle()
pentru fiecaredeferred
funcţie. - În
handle
funcție, din moment cepromise
este deja rezolvat, va apelahandleResolved(self, deferred)
-
handleResolved
funcția va verifica dacă_state === 1
și atribuițicb = deferred.onFulfilled
care este a noastrăthen
sună din nou. Mai tarziutryCallOne(cb, self._value)
vom apela acel apel invers și vom obține rezultatul final. În timp ce faceți acest lucru, atunci când a apărut o eroarepromise
va fi respins.
Când o promisiune este respinsă
În acest caz, toți pașii vor rămâne aceiași – dar în punctul 8 noi sunam reject(reason)
. Acest lucru va suna indirect rejectCallback
definit în doResolve()
și self._state
va deveni 2
. În finale
funcţie cb
va fi egal cu deferred.onRejected
care va fi numit mai târziu de tryCallOne
. Așa este .catch
va fi apelat callback.
Asta este tot pentru acum! Sper că v-a plăcut articolul și vă va ajuta în următorul dvs. interviu JavaScript.
Dacă întâmpinați vreo problemă, nu ezitați Intrați în legătură sau comentează mai jos. Aș fi fericit să vă ajut?
Nu ezitați să bateți din palme dacă ați considerat că este o lectură utilă!
Publicat inițial la 101node.io pe 05 februarie 2019.
#Cum #promite #JavaScript #funcționează #fapt #din #interior #spre #exterior
Cum promite JavaScript funcționează de fapt din interior spre exterior