Как @adamrights указывает в своем ответе , основная проблема в вашем коде состоит в том, что вы не позвонили cbi(err, response)
с правдивым err
1-м параметром, который необходим для остановки async.series
продолжение следующей задачи в очереди.
Теперь ваш код должен работать, но в вашем коде есть повторяющийся шаблон:
function (cbi) {
students.getAll('student', function (err, response) {
// these 3 lines appear in every callback function
if (GLOBAL_VAR_SIGTERM) err = new Error("SIGTERM: Aborting remaining tasks");
if (err) logger.error(err);
cbi(err, response);
// end of repeat pattern
});
}
Ваш обратный вызов, переданный каждой асинхронной задаче, всегда выполняетта же самая 3-х линейная вещь.Мы знаем правило СУХОЙ, всегда полезно извлечь повторяющийся шаблон в другую функцию, чтобы максимально использовать его.
Таким образом, вместо того, чтобы неоднократно объявлять анонимные функции, вы должны объявить фабричную функцию.
function callbackFactory(cbi) {
return function(err, response) {
if (GLOBAL_VAR_SIGTERM) err = new Error("SIGTERM: Aborting remaining tasks");
if (err) logger.error(err);
cbi(err, response);
}
}
// use arrow function to write more concise code
async.series(
[
cbi => students.getAll('student', callbackFactory(cbi)),
cbi => students.deleteAll('student', callbackFactory(cbi)),
cbi => teachers.getAll('teacher', callbackFactory(cbi)),
cbi => teachers.deleteAll('teacher', callbackFactory(cbi)),
]
);
Предварительная тема: использование декоратора для решения сквозных задач
Давайте рассмотрим эту тему немного подробнее.Очевидно, прерывание на раннем этапе при получении SIGTERM
является сквозной проблемой, которую следует отделить от бизнес-логики.Предположим, что ваша бизнес-логика варьируется от задачи к задаче:
async.series(
[
cbi => students.getAll('student', (err, response) => {
if (err) {
logger.error(err);
return cbi(err);
}
updateStudentCount(response.data.length) // <- extra work
cbi(err, response);
}),
cbi => teachers.getAll('student', (err, response) => {
if (err) {
logger.error(err);
return cbi(err);
}
updateTeacherCount(response.data.length) // <- different extra work
cbi(err, response);
})
]
);
Поскольку обратный вызов варьируется, его может быть трудно извлечь из заводской функции, как раньше.С этой точки зрения нам лучше внедрить поведение на раннем этапе прерывания для каждой задачи, чтобы было проще писать нормальную бизнес-логику.
Здесь шаблон декоратора пригодитсяНо глобальная переменная - не лучший инструмент для ее реализации, мы будем использовать прослушиватель событий.
Базовый интерфейс декоратора выглядит следующим образом:
// `task` will be things like `cbi => students.getAll('student', ... )`
function decorateTaskAbortEarly(task) {
return (originalCbi) => {
...
task(originalCbi)
}
}
Ниже приведен наш контрольный список реализации:
- мы собираемся позвонить
originalCbi
, если мы получим SIGTERM
- , но когда мы не получим
SIGTERM
, originalCbi
все еще вызывается внутри обратного вызовалюбой асинхронной задачи, такой как обычная - , если
originalCbi
когда-либо вызывается один раз, мы должны отписаться от SIGTERM
, чтобы предотвратить утечку памяти
Реализация:
function decorateTaskAbortEarly(task) {
return (originalCbi) => {
// subscribe to `SIGTERM`
var listener = () => originalCbi(new Error("SIGTERM: Aborting remaining tasks"));
process.once('SIGTERM', listener);
var wrappedCbi = (err, response) => {
// unsubscribe if `cbi` is called once
process.off('SIGTERM', listener);
return originalCbi(err, response);
};
// pass `cbi` through to `task`
task(wrappedCbi);
}
}
// Usage:
async.series(
[
cbi => students.getAll('student', (err, response) => {
if (err) {
logger.error(err);
return cbi(err);
}
updateStudentCount(response.data.length)
cbi(err, response);
}),
cbi => teachers.getAll('student', (err, response) => {
if (err) {
logger.error(err);
return cbi(err);
}
updateTeacherCount(response.data.length)
cbi(err, response);
})
].map(decorateTaskAbortEarly) // <--- nice API
);