1

Пытаюсь изучить генераторы, написал пример:

function * getData(url){
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);

    xhr.onload = function * () {
        yield this.response;
    };

    xhr.send();
};

var d = getData('/mydata');

console.log(d)

Если я выполню d.next() - то вернется {value: undefined, done: true}. А я хочу сделать так, чтобы в переменной d находилось содержимое страницы /mydata. Ткните меня на ошибку, как сделать правильно?

sanu
  • 2,575

1 Answers1

3

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

Например, вы можете использовать библиотеку co. Код с ее использованием может иметь вид:

let asyncGet = function(data) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(data);
        }, 1000);
    });
};

co(function * () {
    let w = yield asyncGet('W');
    console.log('The first letter is "%s"', w);

    let t = yield asyncGet('T');
    console.log('The second letter is "%s"', t);

    let f = yield asyncGet('F');
    console.log('The third letter is "%s"', f);

    return '' + w + t + f;
}).then(function(word) {
    console.log('The process is done and the word is "%s"', word);
});

А вот и ссылка на JSFiddle.

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

Та самая магическая вызывающая функция устроена довольно просто:

let execute = function(generator) {
    // Рекурсивная функция для вызова следующей итерации генератора
    let run = function(gen, nextVal) {
        let next = gen.next(nextVal);

        if (next.done) {
            // Генератор выполнен. Пора завершать рекурсивный процесс
            // и отдавать результат вовне.
            return Promise.resolve(next.value);
        }

        // В генераторе еще остались значения. Предполагаем, что генератор всегда
        // возвращает Promise. Это очень серьезное допущение и сделано только для простоты
        // примера. Реальные библиотеки (напр. co) позволяют возвращать и другие типы
        // данных.
        return next.value
            // Дожидаемся получения результата и предаем его в следующую итерацию.
            .then(function(data) {
               return run(gen, data);
            })
            // В случае ошибки, бросаем в генератор исключение.   
            .catch(function(err) {
                return gen.throw(err);
            });
    };

    // Создаем экземпляр генератора и инициализируем процесс.
    // Значение, которое предается в генератор при первом вызове gen.next будет
    // проигнорировано, поэтому можем смело использовать null
    return run(generator(), null);
};

В реальных библиотеках этот код несколько сложнее, но принцип остается тем же.

Dmitriy Simushev
  • 17,999
  • 5
  • 49
  • 85