одиночная асинхронная задача
var fetch = require('node-fetch');
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
Чтобы получить окончательный результат выполнения, нужно сделать так:
var g = gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});
Сначала выполните функцию Generator, чтобы получить объект обходчика.
Затем используйте следующий метод для выполнения первой фазы асинхронной задачи, которая называется fetch(url).
Обратите внимание, что поскольку fetch(url) возвращает объект Promise, значение результата равно:
{ value: Promise { <pending> }, done: false }
Наконец, мы добавляем метод then к этому объекту Promise и форматируем возвращаемые данные (data.json()
), а затем вызовите g.next для передачи полученных данных, чтобы можно было выполнить второй этап асинхронной задачи и завершить выполнение кода.
Несколько асинхронных задач
В предыдущем разделе мы вызывали только один интерфейс, поэтому, если бы мы вызывали несколько интерфейсов и использовали несколько yield, не пришлось бы нам продолжать вложение в функцию then...
Итак, давайте рассмотрим случай выполнения нескольких асинхронных задач:
var fetch = require('node-fetch');
function* gen() {
var r1 = yield fetch('https://api.github.com/users/github');
var r2 = yield fetch('https://api.github.com/users/github/followers');
var r3 = yield fetch('https://api.github.com/users/github/repos');
console.log([r1.bio, r2[0].login, r3[0].full_name].join('\n'));
}
Чтобы получить окончательный результат выполнения, вы можете написать:
var g = gen();
var result1 = g.next();
result1.value.then(function(data){
return data.json();
})
.then(function(data){
return g.next(data).value;
})
.then(function(data){
return data.json();
})
.then(function(data){
return g.next(data).value
})
.then(function(data){
return data.json();
})
.then(function(data){
g.next(data)
});
Но я знаю, что ты определенно не хочешь так писать...
Фактически, используя рекурсию, мы можем написать:
function run(gen) {
var g = gen();
function next(data) {
var result = g.next(data);
if (result.done) return;
result.value.then(function(data) {
return data.json();
}).then(function(data) {
next(data);
});
}
next();
}
run(gen);
Ключевым моментом является возврат объекта Promise при выполнении, добавление метода then к объекту Promise, выполнение функции onFullfilled после успешного выполнения асинхронной операции и выполнение g.next в функции onFullfilled, чтобы генератор продолжал выполняться. , а затем возвращает обещание, выполняет g.next в случае успеха, затем возвращает...
функция стартера
В функции запуска запуска форматируем данные в функции thendata.json()
, но в более общих случаях, таких как yield, за которым следует обещание напрямую, а не обещание, возвращаемое функцией выборки, из-за отсутствия метода json код сообщит об ошибке. Итак, для большей общности, вместе с этим примером и лаунчером мы изменим его на:
var fetch = require('node-fetch');
function* gen() {
var r1 = yield fetch('https://api.github.com/users/github');
var json1 = yield r1.json();
var r2 = yield fetch('https://api.github.com/users/github/followers');
var json2 = yield r2.json();
var r3 = yield fetch('https://api.github.com/users/github/repos');
var json3 = yield r3.json();
console.log([json1.bio, json2[0].login, json3[0].full_name].join('\n'));
}
function run(gen) {
var g = gen();
function next(data) {
var result = g.next(data);
if (result.done) return;
result.value.then(function(data) {
next(data);
});
}
next();
}
run(gen);
Просто следуйте объектам Promise после yield, мы можем использовать эту функцию Функция генератора будет запускаться автоматически.
Перезвоните
Должен ли за yield следовать объект Promise, чтобы обеспечить автоматическое выполнение генератора? Что, если это просто функция обратного вызова? Давайте посмотрим на пример:
Сначала давайте смоделируем обычный асинхронный запрос:
function fetchData(url, cb) {
setTimeout(function(){
cb({status: 200, data: url})
}, 1000)
}
Преобразуем эту функцию в:
function fetchData(url) {
return function(cb){
setTimeout(function(){
cb({status: 200, data: url})
}, 1000)
}
}
Для такой функции генератора:
function* gen() {
var r1 = yield fetchData('https://api.github.com/users/github');
var r2 = yield fetchData('https://api.github.com/users/github/followers');
console.log([r1.data, r2.data].join('\n'));
}
Если вы хотите получить окончательный результат:
var g = gen();
var r1 = g.next();
r1.value(function(data) {
var r2 = g.next(data);
r2.value(function(data) {
g.next(data);
});
});
Если написать так, мы столкнемся с той же проблемой, что и в первом разделе, а именно: при использовании нескольких yield код будет вложен в циклы...
Также использует рекурсию, поэтому мы можем преобразовать ее в:
function run(gen) {
var g = gen();
function next(data) {
var result = g.next(data);
if (result.done) return;
result.value(next);
}
next();
}
run(gen);
run
Из этого видно, что автоматическое выполнение функции Генератора требует механизма, то есть, когда асинхронная операция имеет результат, право на выполнение может быть автоматически возвращено.
И два способа сделать это.
(1) Функция обратного вызова. Оберните асинхронную операцию, выставьте функцию обратного вызова и верните право выполнения в функции обратного вызова.
(2) Объект обещания. Оберните асинхронную операцию в объект Promise и верните право на выполнение с помощью метода then.
В двух методах каждый из нас написал функцию запуска запуска, поэтому можем ли мы объединить эти два метода и написать общую функцию запуска? Давай попробуем:
// 第一版
function run(gen) {
var gen = gen();
function next(data) {
var result = gen.next(data);
if (result.done) return;
if (isPromise(result.value)) {
result.value.then(function(data) {
next(data);
});
} else {
result.value(next)
}
}
next()
}
function isPromise(obj) {
return 'function' == typeof obj.then;
}
module.exports = run;
На самом деле реализация очень проста: чтобы определить, является ли result.value промисом, добавьте функцию then или выполните ее напрямую.
return Promise
Мы написали хорошую стартовую функцию, которая поддерживает yield, за которым следует функция обратного вызова или объект Promise.
Теперь есть вопрос для размышления, как нам получить возвращаемое значение функции Генератора? И если в функции Генератора возникает ошибка, например выборка несуществующего интерфейса, как перехватить эту ошибку?
Это очень легко представить в отношении промиса: если эта стартовая функция возвращает промис, мы можем добавить функцию then к этому объекту промиса, когда все асинхронные операции выполняются успешно, мы выполняем функцию onFullfilled, если есть какой-либо сбой, выполняем функция onRejected.
Пишем версию:
// 第二版
function run(gen) {
var gen = gen();
return new Promise(function(resolve, reject) {
function next(data) {
try {
var result = gen.next(data);
} catch (e) {
return reject(e);
}
if (result.done) {
return resolve(result.value)
};
var value = toPromise(result.value);
value.then(function(data) {
next(data);
}, function(e) {
reject(e)
});
}
next()
})
}
function isPromise(obj) {
return 'function' == typeof obj.then;
}
function toPromise(obj) {
if (isPromise(obj)) return obj;
if ('function' == typeof obj) return thunkToPromise(obj);
return obj;
}
function thunkToPromise(fn) {
return new Promise(function(resolve, reject) {
fn(function(err, res) {
if (err) return reject(err);
resolve(res);
});
});
}
module.exports = run;
Есть большое отличие от первого издания:
Во-первых, мы возвращаем обещание, когдаresult.done
Когда true, мы устанавливаем значениеresolve(result.value)
, если во время выполнения возникнет ошибка и будет поймана, мыreject(e)
.
Во-вторых, мы будем использоватьthunkToPromise
Оберните функцию обратного вызова как обещание, а затем единообразно добавьте функцию then. Здесь стоит отметить, что вthunkToPromise
В функции мы сначала следуем принципу ошибки, что означает, что когда мы обрабатываем случай функции обратного вызова:
// 模拟数据请求
function fetchData(url) {
return function(cb) {
setTimeout(function() {
cb(null, { status: 200, data: url })
}, 1000)
}
}
В случае успеха первый параметр должен возвращать значение null, что указывает на отсутствие причины ошибки.
оптимизация
На основе второй редакции мы написали код более лаконично и изящно, окончательный код выглядит следующим образом:
// 第三版
function run(gen) {
return new Promise(function(resolve, reject) {
if (typeof gen == 'function') gen = gen();
// 如果 gen 不是一个迭代器
if (!gen || typeof gen.next !== 'function') return resolve(gen)
onFulfilled();
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise(ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise ' +
'but the following object was passed: "' + String(ret.value) + '"'));
}
})
}
function isPromise(obj) {
return 'function' == typeof obj.then;
}
function toPromise(obj) {
if (isPromise(obj)) return obj;
if ('function' == typeof obj) return thunkToPromise(obj);
return obj;
}
function thunkToPromise(fn) {
return new Promise(function(resolve, reject) {
fn(function(err, res) {
if (err) return reject(err);
resolve(res);
});
});
}
module.exports = run;
co
Если мы сделаем эту стартовую функцию более совершенной, мы будем эквивалентны написанию co. Фактически, приведенный выше код действительно исходит из co...
А что такое со? co — это небольшой модуль, выпущенный TJ Holowaychuk в июне 2013 года для автоматического выполнения функций генератора.
При непосредственном использовании модуля co эти два разных примера можно сократить до:
// yield 后是一个 Promise
var fetch = require('node-fetch');
var co = require('co');
function* gen() {
var r1 = yield fetch('https://api.github.com/users/github');
var json1 = yield r1.json();
var r2 = yield fetch('https://api.github.com/users/github/followers');
var json2 = yield r2.json();
var r3 = yield fetch('https://api.github.com/users/github/repos');
var json3 = yield r3.json();
console.log([json1.bio, json2[0].login, json3[0].full_name].join('\n'));
}
co(gen);
// yield 后是一个回调函数
var co = require('co');
function fetchData(url) {
return function(cb) {
setTimeout(function() {
cb(null, { status: 200, data: url })
}, 1000)
}
}
function* gen() {
var r1 = yield fetchData('https://api.github.com/users/github');
var r2 = yield fetchData('https://api.github.com/users/github/followers');
console.log([r1.data, r2.data].join('\n'));
}
co(gen);
Это особенно полезно?
серия ES6
Адрес каталога серии ES6:GitHub.com/ в настоящее время имеет бриз…
Ожидается, что в серии ES6 будет написано около 20 статей, направленных на углубление понимания некоторых точек знаний ES6, с акцентом на область действия на уровне блоков, шаблоны меток, функции стрелок, реализацию моделирования символов, наборов, карт и обещаний, схему загрузки модулей, асинхронность. обработка и т.п. содержание.
Если есть какие-либо ошибки или неточности, пожалуйста, поправьте меня, большое спасибо. Если вам нравится или у вас есть вдохновение, добро пожаловать в звезду, что также является поощрением для автора.