Jakie jest słowo kluczowe wydajności w JavaScript?


238

Słyszałem o słowie kluczowym „wydajność” w JavaScript, ale znalazłem bardzo słabą dokumentację na jego temat. Czy ktoś może mi wyjaśnić (lub polecić witrynę, która wyjaśnia) jego użycie i do czego służy?


Prawdopodobnie oznacza on „Wydajność” bytes.com/topic/python/answers/685510-yield-keyword-usage
mrówka

4
wyjaśniono to w MDN , ale myślę, że to działa tylko dla Firefoxa, prawda? Jak to jest przenośne? Jakiś sposób na to w Chrome lub node.js? PD: przepraszam, to Javascript v1.7 + , więc jest to właściwość, na którą warto zwrócić uwagę, szukając wsparcia.
Trylks,

1
@Trylks: Generatory są dostępne w węźle od v0.11.2
Janus Troelsen

@JanusTroelsen jednak tylko za flagą. Są wspierani natywnie w ioJS
Dan Pantry

Odpowiedzi:


86

Dokumentacja MDN jest całkiem dobra, IMO.

Funkcja zawierająca słowo kluczowe wydajności jest generatorem. Kiedy go wywołujesz, jego parametry formalne są powiązane z faktycznymi argumentami, ale jego treść nie jest w rzeczywistości oceniana. Zamiast tego zwracany jest iterator generatora. Każde wywołanie metody next () generatora-iteratora wykonuje kolejne przejście przez algorytm iteracyjny. Wartość każdego kroku jest wartością określoną przez słowo kluczowe fed. Pomyśl o wydajności jako o wersji generatora i iteratora, wskazującej granicę między każdą iteracją algorytmu. Za każdym razem, gdy wywołujesz next (), kod generatora jest wznawiany z instrukcji następującej po wydajności.


2
@NicolasBarbulesco istnieje bardzo oczywisty przykład, jeśli przejdziesz do dokumentacji MDN.
Matt Ball

@MattBall - czy funkcja javascript dla PI, taka jak ta, wystarczyłaby w następujący sposób: function * PI {PI = ((Math.SQRT8;) / 9801;); } - czy jest już funkcja zaimplementowana w javascript do tego obliczania PI?
dschinn1001

4
Po co tu cytować MDN? Myślę, że każdy może to przeczytać na MDN. Odwiedź davidwalsh.name/promises, aby dowiedzieć się więcej na ich temat.
Ejaz Karim

20
W jaki sposób uzyskało ~ 80 głosów pozytywnych, gdy (a) jest to kopia „bardzo złej dokumentacji”, jak ją nazywa pytający, i (b) nie mówi nic pomocnego? Znacznie lepsze odpowiedzi poniżej.
www-0av-Com

4
jeśli ktoś poprosi o wyjaśnienie, po prostu skopiuj wklejanie dokumentacji jest całkowicie bezużyteczne. Pytanie oznacza, że ​​przeszukałeś już dokumenty, ale ich nie rozumiesz.
Diego

205

Późne odpowiedzi, prawdopodobnie wszyscy wiedzą o tym yieldteraz, ale pojawiła się lepsza dokumentacja.

Na podstawie przykładu Jamesa Longa z „Javascript's Future: Generators” dla oficjalnego standardu Harmony:

function * foo(x) {
    while (true) {
        x = x * 2;
        yield x;
    }
}

„Kiedy wywołujesz foo, odzyskujesz obiekt Generator, który ma następną metodę.”

var g = foo(2);
g.next(); // -> 4
g.next(); // -> 8
g.next(); // -> 16

To yieldcoś w rodzaju return: dostajesz coś z powrotem. return xzwraca wartość x, ale yield xzwraca funkcję, która daje metodę iteracji w kierunku następnej wartości. Przydatne, jeśli masz procedurę potencjalnie obciążającą pamięć, którą możesz chcieć przerwać podczas iteracji.


13
Pomocne, ale myślę, że function* foo(x){tam jest
Rana Deep

9
@RanaDeep: Składnia funkcji została rozszerzona, aby dodać opcjonalny * token . To, czy jest to potrzebne, zależy od tego, jaką przyszłość powrócisz. Szczegół jest długi: GvR wyjaśnia to dla implementacji Pythona , na której modelowana jest implementacja JavaScript. Używanie function *zawsze będzie właściwe, choć w niektórych przypadkach jest nieco większe niż w functionprzypadku yield.
biskup

1
@ Ajedi32 Tak, masz rację. Harmonia ustandaryzowała korelację między function *i yield, i dodała cytowany błąd („Wczesny błąd powstaje, jeśli wydajność lub wyrażenie * występuje w funkcji innej niż generator”). Ale oryginalna implementacja Javascript 1.7 w Firefoksie nie wymagała* . Zaktualizowałem odpowiednio odpowiedź. Dzięki!
biskup

3
@MuhammadUmer Js w końcu staje się językiem, którego faktycznie można używać. To się nazywa ewolucja.
Lukas Liesis,

1
przykład jest przydatny, ale ... jaka jest funkcja *?
Diego

66

To jest naprawdę proste, tak to działa

  • yieldSłowo kluczowe pomaga po prostu wstrzymać i wznowić funkcję w dowolnym momencie asynchronicznie .
  • Dodatkowo pomaga zwrócić wartość z funkcji generatora .

Weź tę prostą funkcję generatora :

function* process() {
    console.log('Start process 1');
    console.log('Pause process2 until call next()');

    yield;

    console.log('Resumed process2');
    console.log('Pause process3 until call next()');

    let parms = yield {age: 12};
    console.log("Passed by final process next(90): " + parms);

    console.log('Resumed process3');
    console.log('End of the process function');
}

niech _process = process ();

Do czasu wywołania _process.next () to przyzwyczajenie wykonywania przez pierwsze 2 linie kodu, to pierwszy plon będzie wstrzymać funkcję. Aby wznowić funkcję do następnego punktu pauzy ( słowo kluczowe wydajności ), musisz wywołać _process.next () .

Można pomyśleć, że wiele wydajności to punkty przerwania w debuggerze javascript w ramach jednej funkcji. Dopóki nie powiesz, aby przejść do następnego punktu przerwania, nie wykona bloku kodu. ( Uwaga : bez blokowania całej aplikacji)

Ale jednocześnie wykonuje yield to wstrzymać i wznowić zachowań może powrócić pewne rezultaty , jak również {value: any, done: boolean} zgodnie z poprzedniej funkcji my nie emitują żadnych wartości. Jeśli zbadamy poprzednie wyjście, pokaże to samo { value: undefined, done: false } z niezdefiniowaną wartością .

Pozwala zagłębić się w słowo kluczowe wydajności. Opcjonalnie możesz dodać wyrażenie i ustawić przypisanie domyślnej wartości opcjonalnej . (Oficjalna składnia dokumentu)

[rv] = yield [expression];

wyrażenie : wartość do zwrócenia z funkcji generatora

yield any;
yield {age: 12};

rv : Zwraca opcjonalną wartość, która została przekazana do metody next () generatora

Po prostu możesz przekazać parametry do funkcji process () za pomocą tego mechanizmu, aby wykonać różne części wydajności.

let val = yield 99; 

_process.next(10);
now the val will be 10 

Spróbuj teraz

Zastosowania

  • Leniwa ocena
  • Nieskończone sekwencje
  • Asynchroniczne przepływy sterowania

Bibliografia:


54

Upraszczając / opracowując odpowiedź Nicka Sotirosa (co moim zdaniem jest niesamowite), myślę, że najlepiej opisać, jak zacząć kodować yield.

Moim zdaniem największą zaletą używania yieldjest to, że wyeliminuje wszystkie zagnieżdżone problemy zwrotne, które widzimy w kodzie. Trudno na początku zrozumieć, dlatego postanowiłem napisać tę odpowiedź (dla siebie i mam nadzieję, że inni!)

Robi to, wprowadzając ideę rutyny, która jest funkcją, która może dobrowolnie zatrzymać / wstrzymać, dopóki nie osiągnie tego, czego potrzebuje. W javascript jest to oznaczone przez function*. function*Można używać tylko funkcji yield.

Oto typowy javascript:

loadFromDB('query', function (err, result) {
  // Do something with the result or handle the error
})

Jest to niezgrabne, ponieważ teraz cały kod (który oczywiście musi czekać na to loadFromDBwywołanie) musi znajdować się w tym brzydko wyglądającym wywołaniu zwrotnym. Jest to złe z kilku powodów ...

  • Cały twój kod jest wcięty o jeden poziom
  • Masz ten koniec, })którego musisz wszędzie śledzić
  • Cały ten dodatkowy function (err, result)żargon
  • Nie do końca jasne, że robisz to, aby przypisać wartość result

Z drugiej strony, yieldwszystko to można zrobić w jednym wierszu za pomocą ładnego wspólnego schematu.

function* main() {
  var result = yield loadFromDB('query')
}

I tak teraz twoja główna funkcja przyniesie w razie potrzeby, gdy będzie musiała czekać na załadowanie zmiennych i rzeczy. Ale teraz, aby to uruchomić, musisz wywołać normalną (nieskorupcyjną funkcję). Prosta wspólna struktura może rozwiązać ten problem, więc wystarczy, że uruchomisz:

start(main())

I początek jest zdefiniowany (z odpowiedzi Nicka Sotiro)

function start(routine, data) {
    result = routine.next(data);
    if(!result.done) {
        result.value(function(err, data) {
            if(err) routine.throw(err); // continue next iteration of routine with an exception
            else start(routine, data);  // continue next iteration of routine normally
        });
    }
}

A teraz możesz mieć piękny kod, który jest o wiele bardziej czytelny, łatwy do usunięcia i nie musisz majstrować przy wcięciach, funkcjach itp.

Ciekawym spostrzeżeniem jest to, że w tym przykładzie yieldjest to po prostu słowo kluczowe, które można umieścić przed funkcją z wywołaniem zwrotnym.

function* main() {
  console.log(yield function(cb) { cb(null, "Hello World") })
}

Wydrukowałby „Hello World”. Więc możesz faktycznie zmienić dowolną funkcję zwrotną w użycie yield, po prostu tworząc tę ​​samą sygnaturę funkcji (bez cb) i zwracając function (cb) {}, tak jak poniżej:

function yieldAsyncFunc(arg1, arg2) {
  return function (cb) {
    realAsyncFunc(arg1, arg2, cb)
  }
}

Mam nadzieję, że dzięki tej wiedzy możesz napisać czystszy, bardziej czytelny kod, który można łatwo usunąć !


a function*jest zwykłą funkcją bez wydajności?
Abdul,

Myślę, że masz na myśli, że function *jest to funkcja, która zawiera wydajność. Jest to specjalna funkcja zwana generatorem.
Leander

7
Dla osób, które używają już yieldwszędzie, jestem pewien, że ma to większy sens niż wywołania zwrotne, ale nie rozumiem, w jaki sposób jest to bardziej czytelne niż wywołania zwrotne.
palswim

ten artykuł jest trudny do zrozumienia
Martian2049,

18

Aby dać pełną odpowiedź: yielddziała podobnie return, ale w generatorze.

W przypadku często podawanego przykładu działa to w następujący sposób:

function *squareGen(x) {
    var i;
    for (i = 0; i < x; i++) {
        yield i*i;
    }
}

var gen = squareGen(3);

console.log(gen.next().value); // prints 0
console.log(gen.next().value); // prints 1
console.log(gen.next().value); // prints 4

Ale jest też drugi cel słowa kluczowego wydajności. Można go użyć do przesłania wartości do generatora.

Aby wyjaśnić, mały przykład:

function *sendStuff() {
    y = yield (0);
    yield y*y;
}

var gen = sendStuff();

console.log(gen.next().value); // prints 0
console.log(gen.next(2).value); // prints 4

Działa to, jak 2przypisuje się wartość y, wysyłając ją do generatora po zatrzymaniu z pierwszą wydajnością (która zwróciła 0).

To pozwala nam na naprawdę fajne rzeczy. (spójrz na coroutine)



6

yield może być również użyty do wyeliminowania piekła wywołania zwrotnego, dzięki ramowej strukturze.

function start(routine, data) {
    result = routine.next(data);
    if(!result.done) {
        result.value(function(err, data) {
            if(err) routine.throw(err); // continue next iteration of routine with an exception
            else start(routine, data);  // continue next iteration of routine normally
        });
    }
}

// with nodejs as 'node --harmony'
fs = require('fs');
function read(path) {
    return function(callback) { fs.readFile(path, {encoding:'utf8'}, callback); };
}

function* routine() {
    text = yield read('/path/to/some/file.txt');
    console.log(text);
}

// with mdn javascript 1.7
http.get = function(url) {
    return function(callback) { 
        // make xhr request object, 
        // use callback(null, resonseText) on status 200,
        // or callback(responseText) on status 500
    };
};

function* routine() {
    text = yield http.get('/path/to/some/file.txt');
    console.log(text);
}

// invoked as.., on both mdn and nodejs

start(routine());

4

Generator sekwencji Fibonacciego wykorzystujący słowo kluczowe fed.

function* fibbonaci(){
    var a = -1, b = 1, c;
    while(1){
        c = a + b;
        a = b;
        b = c;
        yield c;
    }   
}

var fibonacciGenerator = fibbonaci();
fibonacciGenerator.next().value; // 0 
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 2 

4

Yeild słowo kluczowe w funkcji javaScript powoduje, że jest generatorem,

co to jest generator w javaScript?

Generator to funkcja, która generuje sekwencję wyników zamiast pojedynczej wartości, tj. Generujesz serię wartości

Generatory znaczeń pomagają nam pracować asynchronicznie z iteratorami pomocy. Och, czym są iteratory hacka? naprawdę?

Iteratory to środki, dzięki którym możemy uzyskać dostęp do przedmiotów pojedynczo

skąd iterator pomaga nam uzyskiwać dostęp do elementu pojedynczo? pomaga nam uzyskać dostęp do przedmiotów poprzez funkcje generatora,

funkcje generatora to te, w których używamy yeildsłowa kluczowego, słowo kluczowe wydajności pomaga nam w wstrzymywaniu i wznawianiu wykonywania funkcji

Oto szybki przykład

function *getMeDrink() {

    let question1 = yield 'soda or beer' // execution will pause here because of yield

 if (question1 == 'soda') {

            return 'here you get your soda'

    }

    if (question1 == 'beer') {

        let question2 = yield 'Whats your age' // execution will pause here because of yield

        if (question2 > 18) {

            return "ok you are eligible for it"

        } else {

            return 'Shhhh!!!!'

        }
    }
}


let _getMeDrink = getMeDrink() // initialize it

_getMeDrink.next().value  // "soda or beer"

_getMeDrink.next('beer').value  // "Whats your age"

_getMeDrink.next('20').value  // "ok you are eligible for it"

_getMeDrink.next().value // undefined

pozwól mi wyjaśnić, co się dzieje

zauważyłeś, że wykonanie jest wstrzymywane przy każdym yeildsłowie kluczowym, a my możemy uzyskać do niego dostęp yieldza pomocą iteratora.next()

powoduje to powtarzanie wszystkich yieldsłów kluczowych pojedynczo, a następnie zwraca niezdefiniowane, gdy nie ma już yieldsłów kluczowych w prostych słowach, które można wypowiedziećyield słowo kluczowe jest punktem przerwania, w którym funkcja za każdym razem zatrzymuje się i wznawia tylko po wywołaniu za pomocą iteratora

w naszym przypadku: _getMeDrink.next()jest to przykład iteratora, który pomaga nam uzyskać dostęp do każdego punktu przerwania w funkcji

Przykład generatorów: async/await

jeśli zobaczysz wdrożenie async/await , zobaczysz, że generator functions & promisessą używane do async/awaitpracy

proszę zauważyć, że wszelkie sugestie są mile widziane


3

Zależność między asynchronicznymi wywołaniami javascript.

Kolejny dobry przykład wykorzystania plonu.

function request(url) {
  axios.get(url).then((reponse) => {
    it.next(response);
  })
}

function* main() {
  const result1 = yield request('http://some.api.com' );
  const result2 = yield request('http://some.otherapi?id=' + result1.id );
  console.log('Your response is: ' + result2.value);
}

var it = main();
it.next()


0

Zanim dowiesz się o wydajności, musisz wiedzieć o generatorach. Generatory są tworzone przy użyciu function*składni. Funkcje generatora nie wykonują kodu, ale zwracają typ iteratora zwany generatorem. Gdy wartość jest podana za pomocą nextmetody, funkcja generatora działa, dopóki nie natrafi na słowo kluczowe wydajności. Użycie yieldzwraca obiekt zawierający dwie wartości, jedna jest wartością, a druga jest zakończona (boolean). Wartością może być tablica, obiekt itp.


0

Prosty przykład:

const strArr = ["red", "green", "blue", "black"];

const strGen = function*() {
    for(let str of strArr) {
        yield str;
    }
};

let gen = strGen();

for (let i = 0; i < 5; i++) {
    console.log(gen.next())
}

//prints: {value: "red", done: false} -> 5 times with different colors, if you try it again as below:

console.log(gen.next());

//prints: {value: undefined, done: true}

0

Próbuję również zrozumieć słowo kluczowe wydajności. W oparciu o moje obecne rozumienie, w generatorze słowo kluczowe wydajności działa jak przełącznik kontekstowy procesora. Po uruchomieniu instrukcji dochodu wszystkie stany (na przykład zmienne lokalne) są zapisywane.

Poza tym obiekt wywołujący zostanie zwrócony bezpośrednio do obiektu wywołującego, na przykład {wartość: 0, done: false}. Program wywołujący może użyć tego obiektu wynikowego, aby zdecydować, czy „obudzić” generator ponownie, wywołując next () (wywołanie next () ma na celu iterację wykonania).

Inną ważną rzeczą jest to, że może ustawić wartość zmiennej lokalnej. Tę wartość może przekazać osoba wywołująca „next ()” podczas „budzenia” generatora. na przykład it.next ('valueToPass'), na przykład: „resultValue = return slowQuery (1);” Podobnie jak w przypadku budzenia następnego wykonania, program wywołujący może wstrzyknąć do wykonania jakiś wynik (wstrzyknąć go do zmiennej lokalnej). Zatem dla tego wykonania istnieją dwa rodzaje stanów:

  1. kontekst zapisany w ostatnim wykonaniu.

  2. Wprowadzane wartości przez wyzwalacz tego wykonania.

Dzięki tej funkcji generator może uporządkować wiele operacji asynchronicznych. Wynik pierwszego zapytania asynchronicznego zostanie przekazany do drugiego poprzez ustawienie zmiennej lokalnej (resultValue w powyższym przykładzie). Drugie zapytanie asynchroniczne może zostać wyzwolone tylko przez odpowiedź pierwszego zapytania asynchronicznego. Następnie drugie zapytanie asynchroniczne może sprawdzić wartość zmiennej lokalnej, aby zdecydować o kolejnych krokach, ponieważ zmienna lokalna jest wartością wstrzykniętą z odpowiedzi na pierwsze zapytanie.

Trudności związane z zapytaniami asynchronicznymi są następujące:

  1. piekło zwrotne

  2. tracą kontekst, chyba że przekażą je jako parametry w wywołaniu zwrotnym.

Wydajność i generator mogą pomóc w obu przypadkach.

Bez wydajności i generatora uporządkowanie wielu zapytań asynchronicznych wymaga zagnieżdżonego wywołania zwrotnego z parametrami jako kontekstem, co nie jest łatwe do odczytania i utrzymania.

Poniżej znajduje się przykładowy łańcuch zapytań asynchronicznych działający z nodejs:

const axios = require('axios');

function slowQuery(url) {        
    axios.get(url)
    .then(function (response) {
            it.next(1);
    })
    .catch(function (error) {
            it.next(0);
    })
}

function* myGen(i=0) {
    let queryResult = 0;

    console.log("query1", queryResult);
    queryResult = yield slowQuery('https://google.com');


    if(queryResult == 1) {
        console.log("query2", queryResult);
        //change it to the correct url and run again.
        queryResult = yield slowQuery('https://1111111111google.com');
    }

    if(queryResult == 1) {
        console.log("query3", queryResult);
        queryResult =  yield slowQuery('https://google.com');
    } else {
        console.log("query4", queryResult);
        queryResult = yield slowQuery('https://google.com');
    }
}

console.log("+++++++++++start+++++++++++");
let it = myGen();
let result = it.next();
console.log("+++++++++++end+++++++++++");

Poniżej znajduje się bieżący wynik:

+++++++++++ start +++++++++++

zapytanie1 0

+++++++++++ end +++++++++++

zapytanie2 1

zapytanie4 0

Poniższy wzorzec stanu może zrobić podobnie dla powyższego przykładu:

const axios = require('axios');

function slowQuery(url) {
    axios.get(url)
        .then(function (response) {
            sm.next(1);
        })
        .catch(function (error) {
            sm.next(0);
        })
}

class StateMachine {
        constructor () {
            this.handler = handlerA;
            this.next = (result = 1) => this.handler(this, result);
        }
}

const handlerA = (sm, result) => {
                                    const queryResult = result; //similar with generator injection
                                    console.log("query1", queryResult);
                                    slowQuery('https://google.com');
                                    sm.handler = handlerB; //similar with yield;
                                };

const handlerB = (sm, result) => {
                                    const queryResult = result; //similar with generator injection
                                    if(queryResult == 1) {
                                        console.log("query2", queryResult);
                                        slowQuery('https://1111111111google.com');
                                    }
                                    sm.handler = handlerC; //similar with yield;
                                };

const handlerC = (sm, result) => {
                                    const queryResult = result; //similar with generator injection;
                                    if (result == 1 ) {
                                        console.log("query3", queryResult);
                                        slowQuery('https://google.com');
                                    } else {
                                        console.log("query4", queryResult);
                                        slowQuery('https://google.com');
                                    }
                                    sm.handler = handlerEnd; //similar with yield;
                                };

const handlerEnd = (sm, result) => {};

console.log("+++++++++++start+++++++++++");
const sm = new StateMachine();
sm.next();
console.log("+++++++++++end+++++++++++");

Oto wynik działania:

+++++++++++ start +++++++++++

zapytanie1 0

+++++++++++ end +++++++++++

zapytanie2 1

zapytanie4 0


0

nie zapomnij o bardzo pomocnej składni „x generatora”, aby przejść przez generator. W ogóle nie trzeba używać funkcji next ().

function* square(x){
    for(i=0;i<100;i++){
        x = x * 2;
        yield x;        
    }   
}

var gen = square(2);
for(x of gen){
   console.log(x);
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.