Замедление из-за непараллельного ожидания обещаний в асинхронных генераторах
Я пишу код с использованием генераторов и 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мс, что много.
3 ответа
Вы тратите слишком много времени на ожидание ввода / вывода из разных источников.
В обычном коде обещания вы бы использовали 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
берет объект и ждет разрешения всех его свойств (если они являются обещаниями).
Помните - генераторы и обещания действительно хорошо сочетаются и сочетаются, вы просто получаете обещания:
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 Крисом Ковалем
Как это упоминается в документации 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
выражения, каждое из которых останавливает выполнение, пока конкретное обещание не будет выполнено. Код создаст и выполнит каждую из асинхронных задач последовательно.
Чтобы дождаться нескольких задач параллельно, вы должны yield
массив обещаний. Это будет ждать, пока все они не будут установлены, а затем вернет массив значений результата. Использование 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
};
});
Ответ Бенджамина Грюнбаума верен, но он полностью теряет аспект генератора, что обычно бывает немного, когда вы пытаетесь запустить несколько вещей параллельно. Вы можете, однако, сделать эту работу очень хорошо с 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]
};
});