Javascript 如何在SIGTERM上断开异步系列?

Javascript 如何在SIGTERM上断开异步系列?,javascript,node.js,async.js,sigterm,Javascript,Node.js,Async.js,Sigterm,假设我有以下场景- async.series( [ function (cbi) { students.getAll('student', function (err, response) { if (err) { logger.error(err); } cbi(err, response); }); }, function (cbi) { students.de

假设我有以下场景-

async.series(
  [
    function (cbi) {
      students.getAll('student', function (err, response) {
        if (err) {
          logger.error(err);
        }
        cbi(err, response);
      });
    },
    function (cbi) {
      students.deleteAll('student', function (err, response) {
        if (err) {
          logger.error(err);
        }
        cbi(err, response);
      });
    },
    function (cbi) {
      teachers.getAll('teacher', function (err, response) {
        if (err) {
          logger.error(err);
        }
        cbi(err, response);
      });
    },
    function (cbi) {
      teachers.deleteAll('teacher', function (err, response) {
        if (err) {
          logger.error(err);
        }
        cbi(err, response);
      });
    };
  ]
);
我希望在发送
SIGTERM
时进行优雅的清理。这是对所有学生或所有教师的清理,无论是在发出信号时正在进行的清理,都应完成,下一次清理不应开始

function (cbi) {
  students.getAll('student', function (err, response) {
    if (err || GLOBAL_VAR_SIGTERM === true) {
      logger.error(err);
    }
    cbi(err, response);
  });
}
我想我应该设置一个全局变量来跟踪
SIGTERM
信号

process.on('SIGTERM', function onSigterm () {
  GLOBAL_VAR_SIGTERM = true;
}

有没有更好的方法来中断异步序列以中断
SIGTERM
信号?

如果您想从
async.series()
中响应
SIGTERM
事件,那么您是对的,最简单的方法是使用全局变量进行跟踪

但是您需要将
cbi(err,response)
函数中的第一个参数(error first callback)设置为
true
,以中断序列

因此:

应该更像:

if (err) logger.error(err);
if (GLOBAL_VAR_SIGTERM) err = new Error("SIGTERM: Aborting remaining tasks");
// You could just do err = true
// But best practice is to use an Error instance.

然后,因为
cbi(err,response)
将被调用
err
值等于
true
,其余的任务将不会运行。

正如@adamrights在中指出的,代码中的主要问题是您没有使用truthy
err
第一个参数调用
cbi(err,response)
,这对于停止
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行程序。我们知道干式规则,最好将重复模式提取到另一个函数中,以便尽可能多地重用它

因此,与其重复声明匿名函数,不如声明工厂函数


高级主题:使用decorator处理横切关注点 让我们进一步探讨一下这个话题。显然,在接收
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);
    })
  ]
);
以下是我们的实施清单:

  • 如果我们收到
    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
);
函数decoriteTaskAbortearly(任务){
返回(原始ALCBI)=>{
//订阅“SIGTERM”`
var listener=()=>originalCbi(新错误(“SIGTERM:中止剩余任务”);
进程。一次('SIGTERM',侦听器);
var wrappedCbi=(错误,响应)=>{
//如果呼叫一次“cbi”,请取消订阅
process.off('SIGTERM',侦听器);
返回原始ALCBI(错误、响应);
};
//将“cbi”传递给“任务”`
任务(wrappedCbi);
}
}
//用法:
异步系列(
[
cbi=>students.getAll('student',(err,response)=>{
如果(错误){
记录器错误(err);
返回cbi(err);
}
updateStudentCount(response.data.length)
cbi(err,response);
}),
cbi=>teachers.getAll('student',(err,response)=>{
如果(错误){
记录器错误(err);
返回cbi(err);
}
UpdateCacherCount(response.data.length)
cbi(err,response);
})

].map(decorateTaskAbortEarly)/我喜欢其他答案。这是实现同样目标的另一种方法。我使用自己的示例:

var async = require('async');
var ifAsync = require('if-async')
var GLOBAL_VAR_SIGTERM = false;

async.series({
    one: ifAsync(notsigterm).then(function (callback) {
        setTimeout(function () {
            console.log('one');
            callback(null, 1);
        }, 1000);
    }),
    two: ifAsync(notsigterm).then(function (callback) {
        setTimeout(function () {
            console.log('two');
            callback(null, 2);
        }, 1000);
    }),
    three: ifAsync(notsigterm).then(function (callback) {
        setTimeout(function () {
            console.log('three');
            callback(null, 3);
        }, 1000);
    }),
    four: ifAsync(notsigterm).then(function (callback) {
        setTimeout(function () {
            console.log('four');
            callback(null, 4);
        }, 1000);
    }),
}, function (err, results) {
    if (err) {
        //Handle the error in some way. Here we simply throw it
        //Other options: pass it on to an outer callback, log it etc.
        throw err;
    }
    console.log('Results are ' + JSON.stringify(results));
});

process.on('SIGTERM', function onSigterm () {
    console.log('SIGTERM caught');

  GLOBAL_VAR_SIGTERM = true;
});

function notsigterm(callback) {
    if (!GLOBAL_VAR_SIGTERM) return callback(null, true)
    else return callback(null, false)
}

我正在使用一个名为
ifAsync
的包,它允许您使用谓词
notsigterm
来决定是否应该调用回调。如果
notsigterm
返回true,那么回调将被调用,否则它将被跳过。这是与其他人类似的答案,但不知何故我发现它更干净。如果您有e问题。

if(err)logger.error(err);
应该简单地放在
async.series
的回调中,而不是放在每个单独的回调中。@Bergi我知道,但我只想保留不相关的部分,减少噪音,这样重要的部分会变得更加突出。
// `task` will be things like `cbi => students.getAll('student', ... )`
function decorateTaskAbortEarly(task) {
  return (originalCbi) => {
    ...
    task(originalCbi)
  }
}
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
);
var async = require('async');
var ifAsync = require('if-async')
var GLOBAL_VAR_SIGTERM = false;

async.series({
    one: ifAsync(notsigterm).then(function (callback) {
        setTimeout(function () {
            console.log('one');
            callback(null, 1);
        }, 1000);
    }),
    two: ifAsync(notsigterm).then(function (callback) {
        setTimeout(function () {
            console.log('two');
            callback(null, 2);
        }, 1000);
    }),
    three: ifAsync(notsigterm).then(function (callback) {
        setTimeout(function () {
            console.log('three');
            callback(null, 3);
        }, 1000);
    }),
    four: ifAsync(notsigterm).then(function (callback) {
        setTimeout(function () {
            console.log('four');
            callback(null, 4);
        }, 1000);
    }),
}, function (err, results) {
    if (err) {
        //Handle the error in some way. Here we simply throw it
        //Other options: pass it on to an outer callback, log it etc.
        throw err;
    }
    console.log('Results are ' + JSON.stringify(results));
});

process.on('SIGTERM', function onSigterm () {
    console.log('SIGTERM caught');

  GLOBAL_VAR_SIGTERM = true;
});

function notsigterm(callback) {
    if (!GLOBAL_VAR_SIGTERM) return callback(null, true)
    else return callback(null, false)
}