При использовании генераторов для имитации псевдо-синхронного кода вы забыли об одном очень важном моменте. Для того, чтобы все это работало, вам нужна внешняя функция, которая будет ваш генератор запускать и связывать ввод и вывод генератора.
Например, вы можете использовать библиотеку 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);
};
В реальных библиотеках этот код несколько сложнее, но принцип остается тем же.