Ci sono due domande piuttosto-distinte qui:
- fa o non fare Node.js TCO?
- Come funziona questa funzione di rendimento magico in Node.js?
fa o non fa Node.js fare TCO?
TL; DR: Non più, come del Nodo 8.x. Ha funzionato per un po ', dietro una bandiera o un'altra, ma al momento della stesura di questo documento (novembre 2017) non funziona più perché il motore JavaScript V8 sottostante che utilizza non supporta più il TCO. Vedi this answer per ulteriori informazioni.
Dettagli:
ottimizzazione Tail-call (TCO) è una richiesta part of the ES2015 ("ES6") specification. Quindi supportarlo non è, direttamente, una cosa NodeJS, è qualcosa che il motore JavaScript V8 utilizzato da NodeJS deve supportare.
A partire dal nodo 8.x, V8 non supporta TCO, nemmeno dietro a un flag. Potrebbe fare (di nuovo) ad un certo punto nel futuro; vedi this answer per ulteriori informazioni.
Nodo 7,10 fino a 6.5.0 almeno (i miei appunti dicono 6.2, ma non è d'accordo node.green) supportati TCO dietro una bandiera (--harmony
in 6.6.0 e fino, --harmony_tailcalls
prima) solo in modalità rigorosa.
Se si desidera controllare l'installazione, ecco le prove node.green usi (accertarsi di utilizzare il flag se si sta utilizzando una versione rilevante):
function direct() {
"use strict";
return (function f(n){
if (n <= 0) {
return "foo";
}
return f(n - 1);
}(1e6)) === "foo";
}
function mutual() {
"use strict";
function f(n){
if (n <= 0) {
return "foo";
}
return g(n - 1);
}
function g(n){
if (n <= 0) {
return "bar";
}
return f(n - 1);
}
return f(1e6) === "foo" && f(1e6+1) === "bar";
}
console.log(direct());
console.log(mutual());
$ # Only certain versions of Node, notably not 8.x or (currently) 9.x; see above
$ node --harmony tco.js
true
true
Come funziona questo magico lavoro yield
funziona in Node.js?
Questa è un'altra cosa ES2015 ("funzioni generatore"), quindi è ancora una volta che V8 deve implementare. È completamente implementato nella versione di V8 nel Nodo 6.6.0 (ed è stato utilizzato per diverse versioni) e non è dietro alcun flag.
Le funzioni del generatore (quelle scritte con function*
e yield
) funzionano bloccando e restituendo un iteratore che cattura il loro stato e può essere utilizzato per continuare il loro stato in un'occasione successiva. Alex Rauschmeyer ha un articolo approfondito su di loro here.
Ecco un esempio di utilizzo del iteratore restituito dalla funzione del generatore in modo esplicito, ma di solito non lo farà e vedremo perché in un attimo:
"use strict";
function* counter(from, to) {
let n = from;
do {
yield n;
}
while (++n < to);
}
let it = counter(0, 5);
for (let state = it.next(); !state.done; state = it.next()) {
console.log(state.value);
}
che ha questa uscita:
0
1
2
3
4
Ecco come funziona:
- Quando chiamiamo
counter
(let it = counter(0, 5);
), lo stato interno iniziale della chiamata a counter
viene inizializzato e viene immediatamente restituito un iteratore; nessuno del codice effettivo in counter
viene eseguito (ancora).
- La chiamata
it.next()
esegue il codice in counter
tramite la prima dichiarazione yield
. A quel punto, counter
sospende e memorizza il suo stato interno. it.next()
restituisce un oggetto stato con un flag done
e uno value
. Se il flag done
è false
, il valore value
corrisponde al valore restituito dall'istruzione yield
.
- Ogni chiamata a
it.next()
anticipa lo stato all'interno di counter
al successivo yield
.
- Quando una chiamata a
it.next()
rende counter
finitura e ritorno, l'oggetto stato che ha ottenere indietro done
insieme al true
e value
impostato al valore di ritorno di counter
.
Avendo variabili per l'iteratore e l'oggetto di stato ed effettuare chiamate a it.next()
e l'accesso alle proprietà done
e value
è tutto boilerplate che (di solito) si mette di mezzo di ciò che stiamo cercando di fare, in modo ES2015 fornisce la nuova dichiarazione for-of
che ci rimbocca tutto e ci dà solo ogni valore. Ecco che lo stesso codice di cui sopra scritto con for-of
:
"use strict";
function* counter(from, to) {
let n = from;
do {
yield n;
}
while (++n < to);
}
for (let v of counter(0, 5)) {
console.log(v);
}
v
corrisponde a state.value
nel nostro esempio precedente, con for-of
facendo tutte le chiamate e it.next()
done
controlli per noi.
Eseguire il nodo con il flag '--harmony' per vedere come funziona la seconda versione. per esempio. 'nodo --harmony mytest.js'. Ma prima rivedi l'esempio che citi, ne hai adattato solo una parte al tuo caso. Per quanto riguarda il TCO, la vera domanda è se V8 lo ha implementato - e non c'è menzione di ciò che è stato fatto ancora nel [changelog v8] (https://code.google.com/p/v8/source/browse/trunk/ChangeLog) che posso vedere. –
@ barry-johnson: Ho provato a copiare le funzioni di esempio usando '' yield'' nel secondo link, e Node.js prende l'eccezione a '' function * ''. Questo è uno dei motivi per cui sono confuso. –
Ecco perché ho detto che è necessario eseguire il nodo con l'opzione --harmony. I generatori fanno parte di ES6/Harmony, che non è il default del nodo. –