Что такое ключевое слово yield в JavaScript?

238

Я слышал о ключевом слове yield в JavaScript, но нашел очень плохую документацию по нему. Может ли кто-нибудь объяснить мне (или порекомендовать сайт, который объясняет) его использование и для чего он используется?

mck89
источник
Он, вероятно, имеет в виду «доходность» bytes.com/topic/python/answers/685510-yield-keyword-usage
муравей
4
это объясняется в MDN , но я думаю, что это работает только для Firefox, верно? Насколько портативный это? Любой способ сделать это на Chrome или node.js? PD: извините, это Javascript v1.7 + , так что на это свойство стоит обратить внимание при поиске поддержки.
Trylks
1
@Trylks: Генераторы доступны в Node начиная с v0.11.2
Янус Троелсен
@JanusTroelsen, однако, только за флагом. Они изначально поддерживаются в ioJS
Дэн

Ответы:

86

Документация по MDN довольно хорошая, IMO.

Функция, содержащая ключевое слово yield, является генератором. Когда вы вызываете его, его формальные параметры привязываются к фактическим аргументам, но его тело фактически не оценивается. Вместо этого возвращается генератор-итератор. Каждый вызов метода next () генератора-итератора выполняет очередной проход через итерационный алгоритм. Значение каждого шага - это значение, указанное в ключевом слове yield. Думайте о yield как о генерато-итераторной версии возврата, указывающей границу между каждой итерацией алгоритма. Каждый раз, когда вы вызываете next (), код генератора возобновляется с оператора, следующего за yield.

Мэтт Болл
источник
2
@NicolasBarbulesco есть очень очевидный пример, если вы перейдете к документации MDN.
Мэтт Болл
@MattBall - будет ли функция в виде javascript для PI подобной, была бы достаточна следующим образом: function * PI {PI = ((Math.SQRT8;) / 9801;); } - или уже есть функция, реализованная в javascript для этого расчета PI?
dschinn1001
4
Какой смысл цитировать MDN здесь? Я думаю, что каждый может прочитать это на MDN. Посетите davidwalsh.name/promises, чтобы узнать о них больше.
Эджаз Карим
20
Как это получило ~ 80 голосов, когда (а) это копия «очень плохой документации», как ее называет спрашивающий, и (б) она не говорит ничего полезного? Гораздо лучшие ответы ниже.
www-0av-Com
4
если кто-то просит объяснений, просто скопировать и вставить документацию совершенно бесполезно. Запрос означает, что вы уже искали в документах, но не поняли их.
Диего
205

Поздний ответ, наверное, все знают об этом yieldсейчас, но пришла лучшая документация.

Адаптируем пример из «Будущего Javascript: Генераторы» Джеймса Лонга для официального стандарта Harmony:

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

«Когда вы вызываете foo, вы возвращаете объект Generator, у которого есть следующий метод».

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

Так yieldчто вроде return: ты что-то получаешь. return xвозвращает значение x, но yield xвозвращает функцию, которая дает метод для итерации к следующему значению. Полезно, если у вас есть потенциально ресурсоемкая процедура, которую вы можете прервать во время итерации.

епископ
источник
13
Полезно, но я думаю, ты function* foo(x){здесь
Рана Дип
9
@RanaDeep: Синтаксис функции расширен, чтобы добавить дополнительный * токен . Нужно вам это или нет, зависит от того, какое будущее вы возвращаете. Детали длинные: GvR объясняет это для реализации Python , на которой смоделирована реализация Javascript. Использование function *всегда будет правильным, хотя в некоторых случаях немного больше накладных расходов, чем functionс yield.
епископ
1
@ Ajedi32 Да, ты прав. Гармония стандартизировала соотношение между function *и yieldи добавила указанную ошибку («Ранняя ошибка возникает, если в функции, не являющейся генератором, возникает выражение yield или yield *»). Но оригинальная реализация Javascript 1.7 в Firefox не требовала* . Обновил ответ соответственно. Спасибо!
епископ
3
@MuhammadUmer Js, наконец, становится языком, который вы действительно можете использовать. Это называется эволюция.
Лукас Лиезис
1
пример полезен, но ... что за функция *?
Диего
66

Это действительно просто, вот как это работает

  • yieldКлючевое слово просто помогает приостановить и возобновить функцию в любое время асинхронно .
  • Кроме того, это помогает вернуть значение из функции генератора .

Возьмите эту простую функцию генератора :

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

Пока вы не вызовете _process.next (), он не будет выполнять первые 2 строки кода, а затем первый выход приведет к приостановке функции. Чтобы возобновить функцию до следующей точки паузы ( ключевого слова yield ), вам нужно вызвать _process.next () .

Вы можете думать, что несколько выходов являются точками останова в отладчике javascript внутри одной функции. Пока вы не скажете перейти к следующей точке останова, он не будет выполнять блок кода. ( Примечание : без блокировки всего приложения)

Но в то время как yield выполняет эту паузу и возобновляет поведение, он также может возвращать некоторые результаты в {value: any, done: boolean} соответствии с предыдущей функцией, у нас нет значений. Если мы рассмотрим предыдущий вывод, он покажет то же самое { value: undefined, done: false } со значением undefined .

Давайте перейдем к ключевому слову yield. При желании вы можете добавить выражение и установить присвоить значение по умолчанию . (Официальный синтаксис документа)

[rv] = yield [expression];

выражение : значение, возвращаемое из функции генератора

yield any;
yield {age: 12};

rv : возвращает необязательное значение, переданное методу next () генератора

Просто вы можете передать параметры в функцию process () с помощью этого механизма, чтобы выполнить различные части yield.

let val = yield 99; 

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

Попробуй это сейчас

Обычаи

  • Ленивая оценка
  • Бесконечные последовательности
  • Асинхронные потоки управления

Ссылки:

noelyahan
источник
54

Упрощая / развивая ответ Ника Сотироса (который я считаю удивительным), я думаю, что лучше всего описать, как можно начать кодировать с yield .

На мой взгляд, самое большое преимущество использования yield является то, что оно устранит все проблемы вложенных обратных вызовов, которые мы видим в коде. Трудно понять, как сначала, поэтому я решил написать этот ответ (для себя и, надеюсь, других!)

То, как это происходит, заключается в представлении идеи сопрограммы, которая является функцией, которая может добровольно останавливаться / останавливаться, пока не получит то, что ей нужно. В javascript это обозначено как function*. Только function*функции могут использовать yield.

Вот несколько типичных JavaScript:

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

Это неуклюже, потому что теперь весь ваш код (который, очевидно, должен ждать этого loadFromDBвызова) должен быть внутри этого уродливого обратного вызова. Это плохо по нескольким причинам ...

  • Весь ваш код имеет отступ на один уровень в
  • У вас есть этот конец, })который нужно отслеживать везде
  • Все это дополнительный function (err, result)жаргон
  • Не совсем понятно, что вы делаете это, чтобы присвоить значение result

С другой стороны, yieldвсе это можно сделать в одну строку с помощью удобной сопрограммы.

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

И теперь ваша основная функция будет работать там, где это необходимо, когда ей нужно ждать загрузки переменных и вещей. Но теперь, чтобы запустить это, вам нужно вызвать нормальную функцию (не сопрограмму). Простая сопрограммная среда может решить эту проблему, так что все, что вам нужно сделать, это запустить это:

start(main())

И начало определено (из ответа Ника Сотиро)

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

И теперь у вас может быть красивый код, который будет гораздо более читабельным, легко удаляемым, и вам не нужно возиться с отступами, функциями и т. Д.

Интересное наблюдение заключается в том, что в этом примере yieldэто просто ключевое слово, которое можно поставить перед функцией с обратным вызовом.

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

Напечатал бы "Hello World". Таким образом, вы можете превратить любую функцию обратного вызова в использование yield, просто создав одну и ту же сигнатуру функции (без cb) и вернув ее function (cb) {}, вот так:

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

Надеемся, что благодаря этим знаниям вы сможете написать более понятный и читаемый код, который легко удалить !

Leander
источник
а function*обычная функция без выхода?
Абдул
Я думаю, вы имеете в виду, что function *это функция, которая содержит доходность. Это специальная функция, называемая генератором.
Леандер
7
Для людей, которые уже используют yieldвезде, я уверен, что это имеет больше смысла, чем обратные вызовы, но я не вижу, как это более читабельно, чем обратные вызовы.
Palswim
эту статью трудно понять
Martian2049
18

Чтобы дать полный ответ: yieldработает аналогично return, но в генераторе.

Что касается обычно данного примера, это работает следующим образом:

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

Но есть и вторая цель ключевого слова yield. Может использоваться для отправки значений в генератор.

Чтобы уточнить, небольшой пример:

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

Это работает, поскольку значение 2присваивается yпутем отправки его генератору после того, как он остановился на первом выходе (который вернул 0).

Это позволяет нам делать действительно интересные вещи. (ищите сопрограмму)

Дэвид
источник
16

Используется для генераторов итераторов. По сути, это позволяет вам создать (потенциально бесконечную) последовательность, используя процедурный код. Смотрите документацию Mozilla .

Мэтью Флэшен
источник
6

yield также может быть использован для устранения обратного вызова ада, с сопрограммой каркаса.

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

Генератор последовательности Фибоначчи с использованием ключевого слова yield.

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 
nikksan
источник
4

Yeild ключевое слово в функции javaScript делает его генератором,

что такое генератор в javaScript?

Генератор - это функция, которая выдает последовательность результатов вместо одного значения, т.е. вы генерируете серию значений

Генераторы значений помогают нам работать асинхронно с помощью итераторов. А теперь, что за хакерские итераторы? действительно?

Итераторы - это средство, с помощью которого мы можем получить доступ к элементам по одному

откуда итератор помогает нам получить доступ к элементу по одному за раз? это помогает нам получить доступ к элементам через функции генератора,

Функции генератора - это те, в которых мы используем yeildключевое слово, а ключевое слово yield помогает нам в приостановке и возобновлении выполнения функции.

вот быстрый пример

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

позвольте мне кратко объяснить, что происходит

вы заметили, что выполнение приостановлено для каждого yeildключевого слова, и мы можем получить доступ сначала yieldс помощью итератора.next()

это повторяет все yieldключевые слова по одному, а затем возвращает неопределенное значение, когда больше не осталось yieldключевых слов в простых словах, которые вы можете сказатьyield ключевое слово является точкой останова, где функция каждый раз останавливается и возобновляется только при вызове с использованием итератора

для нашего случая: _getMeDrink.next()это пример итератора, который помогает нам получить доступ к каждой точке останова в функции

Пример генераторов: async/await

если вы видите реализацию async/await вы увидите generator functions & promises, используются, чтобы сделать async/awaitработу

пожалуйста, укажите любые предложения приветствуется

Ханзла Хабиб
источник
3

Зависимость между асинхронными вызовами JavaScript.

Еще один хороший пример того, как можно использовать урожай.

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

Прежде чем вы узнаете о доходности, вы должны знать о генераторах. Генераторы создаются с использованием function*синтаксиса. Функции генератора не выполняют код, а вместо этого возвращают тип итератора, называемый генератором. Когда значение дается с помощью nextметода, функция генератора продолжает выполняться, пока не встретит ключевое слово yield. Использование yieldвозвращает объект, содержащий два значения: одно значение, а другое готово (логическое значение). Значение может быть массивом, объектом и т. Д.

JuicY_Burrito
источник
0

Простой пример:

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

Я также пытаюсь понять ключевое слово yield. Исходя из моего текущего понимания, в генераторе ключевое слово yield работает как переключатель контекста процессора. Когда выполняется оператор yield, все состояния (например, локальные переменные) сохраняются.

Кроме того, объект прямого результата будет возвращен вызывающей стороне, например, {value: 0, done: false}. Вызывающий объект может использовать этот объект результата, чтобы решить, следует ли снова «разбудить» генератор, вызвав next () (вызов next () должен выполнить итерацию выполнения).

Еще одна важная вещь заключается в том, что он может установить значение для локальной переменной. Это значение может быть передано вызывающей функцией next () при «пробуждении» генератора. например, it.next ('valueToPass'), например: "resultValue = yield slowQuery (1);" Так же, как при пробуждении следующего выполнения, вызывающая сторона может ввести какой-либо результат выполнения в выполнение (вставив его в локальную переменную). Таким образом, для этого выполнения существует два вида состояния:

  1. контекст, сохраненный в последнем исполнении.

  2. Введенные значения триггером этого выполнения.

Таким образом, с помощью этой функции генератор может сортировать несколько асинхронных операций. Результат первого асинхронного запроса будет передан второму путем установки локальной переменной (resultValue в примере выше). Второй асинхронный запрос может быть вызван только ответом первого асинхронного запроса. Затем второй асинхронный запрос может проверить значение локальной переменной, чтобы решить следующие шаги, поскольку локальная переменная является введенным значением из ответа первого запроса.

Трудности асинхронных запросов:

  1. ад обратного вызова

  2. потеря контекста, если не передать их в качестве параметров в обратном вызове.

yield и генератор могут помочь в обоих случаях.

Без yield и генератора для сортировки нескольких асинхронных запросов требуется вложенный обратный вызов с параметрами в качестве контекста, который нелегко читать и поддерживать.

Ниже приведен пример цепочки асинхронных запросов, выполняющихся с помощью 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+++++++++++");

Ниже приведен результат выполнения:

+++++++++++ начать +++++++++++

query1 0

+++++++++++ конец +++++++++++

query2 1

query4 0

Шаблон состояния ниже может сделать то же самое для приведенного выше примера:

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

Ниже приводится результат выполнения:

+++++++++++ начать +++++++++++

query1 0

+++++++++++ конец +++++++++++

query2 1

query4 0

Пит
источник
0

не забывайте очень полезный синтаксис «x of generator» для обхода генератора. Нет необходимости использовать функцию 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);
}
Джон Ктехик
источник