Замедление из-за непараллельного ожидания promises в асинхронных генераторах
Я пишу код с использованием генераторов и Bluebird, и у меня есть следующее:
var async = Promise.coroutine;
function Client(request){
this.request = request;
}
Client.prototype.fetchCommentData = async(function* (user){
var country = yield countryService.countryFor(user.ip);
var data = yield api.getCommentDataFor(user.id);
var notBanned = yield authServer.authenticate(user.id);
if (!notBanned) throw new AuthenticationError(user.id);
return {
country: country,
comments: data,
notBanned: true
};
});
Однако, это довольно медленно, я чувствую, что мое приложение слишком много ждет для ввода-вывода, и это не параллельно. Как повысить производительность моего приложения?
Общее время отклика составляет 800 для countryFor
+ 400 для getCommentDataFor
+ 600 для authenticate
, поэтому всего 1800 мс, что много.
Ответы
Ответ 1
Вы тратите слишком много времени на ожидание ввода-вывода из разных источников.
В нормальном коде обещания вы использовали бы Promise.all
для этого, однако - люди склонны писать код, ожидающий запросов с генераторами. Ваш код выполняет следующие действия:
<-client service->
countryFor..
''--..
''--..
''--.. country server sends response
..--''
..--''
..--''
getCommentDataFor
''--..
''--..
''--..
''--.. comment service returns response
..--''
..--''
..--''
authenticate
''--..
''--..
''--.. authentication service returns
..--''
..--''
..--''
Generator done.
Вместо этого он должен делать:
<-client service->
countryFor..
commentsFor..''--..
authenticate..''--..''--..
''--..''--..''--.. country server sends response
''--..--''.. comment service returns response
..--''..--''.. authentication service returns response
..--''..--''..
..--''..--''..--''
..--''..--''
..--''
Generator done
Проще говоря, все ваши операции ввода-вывода должны выполняться параллельно.
Чтобы исправить это, я бы использовал Promise.props
. Promise.props
принимает объекты и ждет, пока все его свойства будут разрешены (если они promises).
Помните - генераторы и promises хорошо сочетаются и соответствуют, вы просто получаете promises:
Client.prototype.fetchCommentData = async(function* (user){
var country = countryService.countryFor(user.ip);
var data = api.getCommentDataFor(user.id);
var notBanned = authServer.authenticate(user.id).then(function(val){
if(!val) throw new AuthenticationError(user.id);
});
return Promise.props({ // wait for all promises to resolve
country : country,
comments : data,
notBanned: notBanned
});
});
Это очень распространенная ошибка, которую люди делают при использовании генераторов в первый раз.
искусство ascii бесстыдно взято из Q-Connection Крисом Ковалем
Ответ 2
Как уже упоминалось в документах Bluebird для Promise.coroutine
, вам нужно следить за тем, чтобы не yield
в серии.
var county = yield countryService.countryFor(user.ip);
var data = yield api.getCommentDataFor(user.id);
var notBanned = yield authServer.authenticate(user.id);
Этот код имеет 3 выражения yield
, каждый из которых останавливает выполнение до тех пор, пока не будет определено конкретное обещание. Код будет создавать и выполнять каждую из задач async последовательно.
Чтобы подождать несколько задач параллельно, вы должны yield
массив promises. Это будет ждать, пока все они будут установлены, а затем вернет массив значений результата. Использование назначений для деструктуризации ES6 приводит к краткому коду для этого:
Client.prototype.fetchCommentData = async(function* (user){
var [county, data, notBanned] = yield [
// a single yield only: ^^^^^
countryService.countryFor(user.ip),
api.getCommentDataFor(user.id),
authServer.authenticate(user.id)
];
if (!notBanned)
throw new AuthenticationError(user.id);
return {
country: country,
comments: data,
notBanned: true
};
});
Ответ 3
Ответ Benjamin Gruenbaum верен, но он полностью утрачивает полностью генераторский аспект, который, как правило, случается, когда вы пытаетесь параллельно запускать несколько вещей. Тем не менее, вы можете сделать эту работу отлично с помощью ключевого слова yield
. Я также использую некоторые дополнительные функции ES6, такие как назначения деструктурирования и сокращение инициализатора объекта:
Client.prototype.fetchCommentData = async(function* (user){
var country = countryService.countryFor(user.ip);
var data = api.getCommentDataFor(user.id);
var notBanned = authServer.authenticate(user.id).then(function(val){
if(!val) throw new AuthenticationError(user.id);
});
// after each async operation finishes, reassign the actual values to the variables
[country, data, notBanned] = yield Promise.all([country, data, notBanned]);
return { country, data, notBanned };
});
Если вы не хотите использовать эти дополнительные функции ES6:
Client.prototype.fetchCommentData = async(function* (user){
var country = countryService.countryFor(user.ip);
var data = api.getCommentDataFor(user.id);
var notBanned = authServer.authenticate(user.id).then(function(val){
if(!val) throw new AuthenticationError(user.id);
});
var values = yield Promise.all([country, data, notBanned]);
return {
country: values[0],
data: values[1],
notBanned: values[2]
};
});