JavaScript'teki verim anahtar kelimesi nedir?


238

JavaScript'te bir "verim" anahtar kelimesi duydum, ancak bununla ilgili çok kötü belgeler buldum. Birisi bana kullanımını ve ne için kullanıldığını açıklayabilir (veya açıklayan bir site önerebilir)?



4
MDN'de açıklandı , ama bence bu sadece firefox için geçerli, değil mi? Ne kadar taşınabilir? Bunun Chrome veya node.js'de herhangi bir yolu var mı? PD: üzgünüm, Javascript v1.7 + , bu yüzden destek ararken bakmanız gereken özellik.
Trylks

1
@Trylks: Jeneratörler v0.11.2'den beri Düğümde mevcut
Janus Troelsen

@JanusTroelsen, sadece bir bayrak arkasında. Bunlar ioJS
Dan

Yanıtlar:


86

MDN dokümanları IMO, oldukça iyidir.

Ürün anahtar kelimesini içeren işlev bir oluşturucudur. Bunu çağırdığınızda, resmi parametreleri gerçek bağımsız değişkenlere bağlıdır, ancak gövdesi aslında değerlendirilmez. Bunun yerine, bir jeneratör-yineleyici döndürülür. Generator-iterator'ın next () yöntemine yapılan her çağrı, yinelemeli algoritmadan başka bir geçiş gerçekleştirir. Her adımın değeri, verim anahtar kelimesi tarafından belirtilen değerdir. Verimi, algoritmanın her yinelemesi arasındaki sınırı belirten, geri dönüşün jeneratör-yineleyici versiyonu olarak düşünün. Next () öğesini her aradığınızda, jeneratör kodu verimi izleyen ifadeden devam eder.


2
@NicolasBarbulesco, MDN belgelerine tıklarsanız çok açık bir şekilde yerleştirilmiş bir örnek var.
Matt Ball

@MattBall - bunun gibi PI için javascript işlevi aşağıdaki gibi yeterlidir: function * PI {PI = ((Math.SQRT8;) / 9801;); } - veya bu PI hesaplaması için javascript'te zaten uygulanmış bir fonksiyon var mı?
dschinn1001

4
MDN'yi burada alıntılamanın anlamı nedir? Bence herkes bunu MDN'de okuyabilir. Onlar hakkında daha fazla bilgi edinmek için davidwalsh.name/promises adresini ziyaret edin .
Ejaz Karim

20
(A) soru soran kişinin "çok zayıf belgelerin" bir kopyası olması ve (b) yararlı bir şey söylememesi durumunda bu ~ 80 upvotes'u nasıl elde etti? Aşağıda çok daha iyi cevaplar.
www-0av-Com

4
Birisi açıklama isterse, sadece bir dokümanı yapıştırmak tamamen kullanışsızdır. Sormak, zaten dokümanlarda arama yaptığınız, ancak anlamadığınız anlamına gelir.
Diego

205

Geç cevaplama, muhtemelen herkes yieldşimdi biliyor , ancak daha iyi belgeler geldi.

James Harmony'nin resmi Harmony standardı için "Javascript'in Geleceği: Jeneratörler" in bir örneğini uyarlamak :

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

"Foo'yu çağırdığınızda, bir sonraki yönteme sahip bir Generator nesnesini geri alırsınız."

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

Yani yieldnazik benzeri olan return: bir şey kolla. return xdeğerini döndürür x, ancak yield xbir sonraki değere doğru yineleme yöntemi sağlayan bir işlev döndürür. Faydalı bir varsa potansiyel bellek yoğun prosedürü size yineleme sırasında kesmek isteyebilirsiniz.


13
Yararlı, ama sanırım function* foo(x){orada
Rana Deep

9
@RanaDeep: İşlev sözdizimi, isteğe bağlı bir * belirteç eklemek için genişletilir . İhtiyacınız olup olmadığı iade ettiğiniz geleceğe bağlıdır. Ayrıntı uzun: GvR bunu Javascript uygulamasının modellentiği Python uygulaması için açıklıyor . Kullanımı function *her zaman biraz daha masraf daha bazı durumlarda da, doğru olacaktır functionile yield.
piskopos

1
@ Ajedi32 Evet, haklısın. Uyum , ve arasındaki korelasyonu standardize etti function *ve yieldbelirtilen hatayı ekledi ("Jeneratör olmayan bir fonksiyonda bir verim veya verim * ifadesi meydana gelirse erken bir hata ortaya çıkar"). Ama, Firefox'ta orijinal JavaScript 1.7 uygulaması gerektirmeyen* . Cevap buna göre güncellendi. Teşekkürler!
fil

3
@MuhammadUmer Js sonunda kullanabileceğiniz bir dil haline geliyor. Buna evrim denir.
Lukas Liesis

1
örnek yararlıdır, ama ... bir işlev nedir *?
Diego

66

Gerçekten Basit, Nasıl Çalışır

  • yieldanahtar kelime, bir işlevi zaman uyumsuz olarak duraklatmaya ve devam ettirmeye yardımcı olur .
  • Ayrıca bir jeneratör fonksiyonundan değer döndürmeye yardımcı olur .

Bu basit jeneratör işlevini kullanın:

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');
}

let _process = process ();

Aradığınızda kadar () _process.next o alışkanlık yürütmek ilk 2 çizgiler kodun ardından ilk verim olacaktır pause fonksiyonu. Bir sonraki duraklama noktasına ( verim anahtar kelimesi ) kadar işlevi sürdürmek için _process.next () öğesini çağırmanız gerekir .

Birden çok verimin , tek bir işlev içindeki bir javascript hata ayıklayıcısındaki kesme noktaları olduğunu düşünebilirsiniz . Bir sonraki kesme noktasında gezinmeyi söyleyene kadar kod bloğunu yürütmez. ( Not : tüm uygulamayı engellemeden)

Ama verim gerçekleştirdiği bu duraklatma ve devam etme davranışları ederken yapabilirsiniz bazı sonuç sıra {value: any, done: boolean} biz tüm değerleri yayarlar değil önceki fonksiyonuna göre. Önceki çıktıyı araştırırsak, tanımsız{ value: undefined, done: false } değerle aynı şeyi gösterir .

Ürün anahtar kelimesine girelim. İsteğe bağlı olarak ifade ekleyebilir ve varsayılan bir isteğe bağlı değer atayabilirsiniz . (Resmi doküman sözdizimi)

[rv] = yield [expression];

expression : Generator fonksiyonundan döndürülecek değer

yield any;
yield {age: 12};

rv : Üretecin next () yöntemine iletilen isteğe bağlı değeri döndürür

Farklı verim parçalarını yürütmek için parametreleri bu mekanizma ile process () işlevine iletebilirsiniz.

let val = yield 99; 

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

Şimdi dene

Usages

  • Tembel değerlendirme
  • Sonsuz diziler
  • Asenkron kontrol akışları

Referanslar:


54

Nick Sotiros'un cevabını basitleştiren / ayrıntılandıran (ki bu harika olduğunu düşünüyorum), birinin kodlamaya nasıl başlayacağını tanımlamanın en iyisi olduğunu düşünüyorum yield.

Kanımca, kullanmanın en büyük avantajı, yieldkodda gördüğümüz tüm iç içe geri arama sorunlarını ortadan kaldırmasıdır. İlk başta nasıl olduğunu görmek zor, bu yüzden bu cevabı yazmaya karar verdim (kendim ve umarım diğerleri için!)

Yaptığı yol, ihtiyaç duyduğu şeyi alana kadar gönüllü olarak durdurabilen / duraklatabilen bir işlev olan bir rutin fikrini tanıtmaktır. Javascript'te, bu ile gösterilir function*. Yalnızca function*fonksiyonlar kullanılabilir yield.

İşte bazı tipik javascriptler:

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

Bu hantal çünkü şimdi kodunuzun (ki bu loadFromDBçağrıyı beklemesi gerekiyor) bu çirkin görünümlü geri arama içinde olması gerekiyor. Bu birkaç nedenden dolayı kötü ...

  • Kodunuzun tümü bir seviye girintili
  • Her })yerde takip etmeniz gereken bu sonunuz var
  • Bütün bu ekstra function (err, result)jargon
  • Bunu bir değer atamak için yaptığınızı tam olarak belli değil result

Öte yandan, yieldtüm bunlar hoş bir rutin çerçevenin yardımıyla tek bir satırda yapılabilir .

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

Ve şimdi ana fonksiyonunuz, değişkenlerin ve şeylerin yüklenmesini beklemesi gerektiğinde, gerektiğinde verilecektir. Ama şimdi, bunu çalıştırmak için normal bir (koroutin olmayan fonksiyon) çağırmanız gerekir. Basit bir rutin çerçeve bu sorunu düzeltebilir, böylece tek yapmanız gereken bunu çalıştırmaktır:

start(main())

Ve başlangıç ​​tanımlandı (Nick Sotiro'nun cevabından)

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
        });
    }
}

Ve şimdi, çok daha okunabilir, silinmesi kolay ve girintiler, işlevler vb. İle uğraşmanıza gerek olmayan güzel kodlara sahip olabilirsiniz.

İlginç bir gözlem, bu örnekte, yieldaslında bir geri arama işlevinden önce koyabileceğiniz bir anahtar kelime olmasıdır.

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

"Merhaba Dünya" yazdırır. Böylece yield, sadece aynı işlev imzasını (cb olmadan) oluşturarak ve geri dönerek function (cb) {}, herhangi bir geri çağırma işlevini kullanıma dönüştürebilirsiniz :

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

Umarım bu bilgi ile silinmesi kolay daha temiz, daha okunabilir bir kod yazabilirsiniz !


a function*sadece verimsiz düzenli bir işlev midir?
Abdul

Bence function *bu verim içeren bir işlev . Jeneratör adı verilen özel bir işlev.
Leander

7
Zaten yieldher yerde kullanan insanlar için , bunun geri aramalardan daha mantıklı olduğuna eminim, ancak bunun geri aramalardan nasıl daha okunabilir olduğunu göremiyorum.
palswim

bu makaleyi anlamak zor
Martian2049

18

Tam bir cevap vermek için: yieldbenzer return, ancak bir jeneratörde çalışıyor.

Yaygın olarak verilen örneğe gelince, bu şu şekilde çalışır:

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

Ancak, anahtar kelime veriminin ikinci bir amacı da vardır. Jeneratöre değer göndermek için kullanılabilir.

Açıklığa kavuşturmak için küçük bir örnek:

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

Bu, değer 2atandığı gibi y, ilk verimde (geri dönen 0) durduktan sonra jeneratöre gönderilerek çalışır .

Bu bize gerçekten korkak şeyler yapmamızı sağlıyor. (programa bak)



6

yield Coroutine çerçeveli geri arama cehennemini ortadan kaldırmak için de kullanılabilir.

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

Ürün anahtar kelimesini kullanarak Fibonacci dizi üreteci.

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 javaScript işlevindeki anahtar kelime, onu jeneratör yapar,

javaScript'te jeneratör nedir?

Jeneratör, tek bir değer yerine bir sonuç dizisi üreten bir işlevdir, yani bir dizi değer üretirsiniz

Anlam üreteçleri yardım yineleyicilerle eşzamansız çalışmamıza yardımcı olur, şimdi hack yineleyiciler nelerdir? Gerçekten mi?

Yineleyiciler, öğelere birer birer erişebildiğimiz anlamına gelir

yineleyici öğeye birer birer erişmemize nereden yardım eder? jeneratör fonksiyonlarıyla öğelere erişmemize yardımcı olur,

jeneratör fonksiyonları kullandığımız işlevlerdir yeild anahtar kelime anahtar kelime, işlevin yürütülmesini duraklatmaya ve devam ettirmemize yardımcı olur

işte hızlı örnek

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

neler olduğunu kısaca açıklayayım

her birinde yürütmenin duraklatıldığını fark ettiniz yeild anahtar kelimede ve ilk önce yieldyineleyici yardımıyla erişebiliyoruz.next()

bu, tüm yieldanahtar kelimeleri teker teker yineler ve daha sonra artık tanımsızsa döndürüryield basit kelimelerle söylenebilecek anahtar kelime kalmadığındayield kelimenin her seferinde işlev durakladığı ve yalnızca yineleyici kullanarak aradığınızda devam ettiği kesme noktası

bizim durumumuz için: _getMeDrink.next() bu, işlevdeki her kırılma noktasına erişmemize yardımcı olan yineleyicinin bir örneğidir

Jeneratör Örneği: async/await

Eğer uygulamanızın yapıldığını async/await göreceksinizgenerator functions & promisesasync/await

herhangi bir öneri memnuniyetle işaret lütfen


3

Zaman uyumsuz javascript çağrıları arasındaki bağımlılık.

Verimin nasıl kullanılabileceğine bir başka iyi örnek.

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

Verimi öğrenmeden önce jeneratörler hakkında bilmeniz gerekir. Jeneratörler function*sözdizimi kullanılarak oluşturulur . Jeneratör işlevleri kod yürütmez, bunun yerine jeneratör adı verilen bir yineleyici türü döndürür. nextYöntem kullanılarak bir değer verildiğinde , jeneratör işlevi bir verim anahtar sözcüğüyle karşılaşana kadar çalışmaya devam eder. Kullanarak yieldbiri iki değer içeren bir nesne geri verir, diğeri değer diğeri yapılır (boolean). Değer bir dizi, nesne vb. Olabilir.


0

Basit bir örnek:

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

Ayrıca verim anahtar kelimesini anlamaya çalışıyorum. Mevcut anlayışımı temel alarak, jeneratörde, verim anahtar sözcüğü bir CPU bağlam anahtarı gibi çalışır. Verim ifadesi çalıştırıldığında, tüm durumlar (örneğin, yerel değişkenler) kaydedilir.

Bunun yanı sıra, {value: 0, done: false} gibi doğrudan bir sonuç nesnesi arayana döndürülecektir. Arayan bu sonuç nesnesini, next () öğesini çağırarak jeneratörü 'uyandırmaya' karar vermek için kullanabilir.

Bir başka önemli şey de yerel bir değişkene değer ayarlayabilmesidir. Bu değer, jeneratörü 'uyandırırken' 'next ()' arayan tarafından geçirilebilir. örneğin, it.next ('valueToPass'), şöyle: "sonuçDeğer = verim yavaşQuery (1);" Bir sonraki yürütmeyi uyandırdığı gibi, arayan da yürütmeye bazı çalışan sonuçlar verebilir (yerel değişkene enjekte ederek). Dolayısıyla, bu infaz için iki tür devlet vardır:

  1. son yürütmede kaydedilen bağlam.

  2. Bu yürütmenin tetikleyicisi tarafından enjekte edilen değerler.

Bu nedenle, bu özellik ile jeneratör birden fazla zaman uyumsuz işlemi sıralayabilir. İlk eşzamansız sorgunun sonucu, yerel değişken (yukarıdaki örnekte sonuçDeğeri) ayarlanarak ikincisine iletilir. İkinci eşzamansız sorgu yalnızca ilkinin eşzamansız sorgu yanıtıyla tetiklenebilir. Daha sonra ikinci async sorgusu, yerel değişken ilk sorgunun yanıtından enjekte edilen bir değer olduğundan, sonraki adımlara karar vermek için yerel değişken değerini kontrol edebilir.

Zaman uyumsuz sorguların zorlukları:

  1. geri arama cehennemi

  2. geri aramada parametre olarak geçmedikçe bağlam kaybı.

verim ve jeneratör her ikisinde de yardımcı olabilir.

Verim ve üreteç olmadan, birden fazla zaman uyumsuz sorguyu sıralamak, okunması ve bakımı kolay olmayan bağlam olarak parametrelerle iç içe geri arama gerektirir.

Aşağıda nodejs ile çalışan zincirleme zaman uyumsuz sorgu örneği:

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+++++++++++");

Çalışan sonuç aşağıdadır:

başlamak +++++++++++ +++++++++++

sorgu1 0

+++++++++++ uç +++++++++++

sorgu2 1

sorgu4 0

Aşağıdaki durum örneği yukarıdaki örnek için benzer bir şey yapabilir:

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+++++++++++");

Çalışan sonuç aşağıdadır:

başlamak +++++++++++ +++++++++++

sorgu1 0

+++++++++++ uç +++++++++++

sorgu2 1

sorgu4 0


0

jeneratör içinde döngü için çok yararlı 'jeneratör' sözdizimi unutmayın. Bir sonraki () işlevini kullanmaya gerek yoktur.

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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.