如何在任务失败时立即中断后续串行队列
解读
面试官真正想考察的是你对 Grunt 串行任务链(task queue) 的容错机制是否熟悉。
国内项目普遍把「编译 → 代码检查 → 单元测试 → 打包 → 上传」写成一条串行链,任何一步报错如果继续往下跑,轻则把脏代码发到预发环境,重则把线上资源覆盖掉。
能否在第一步失败就立刻退出进程,是衡量候选人是否具备「工程化安全意识」的硬指标。
回答时要同时给出配置层(Gruntfile) 和运行时(CLI) 两种做法,并指出 Grunt 1.0 之后默认行为的差异,才能体现“资深”。
知识点
- --force 与 --no-force
默认情况下 Grunt 1.0 已经不再自动继续,但国内很多老项目仍停留在 0.4.x,候选人必须知道老版本需要显式关闭--force。 - this.async() 的两种用法
任务内部如果调用var done = this.async();,一旦传入done(false)或抛异常,Grunt 核心会标记该任务失败。 - grunt.fail 模块
grunt.fail.fatal()会立即终止整个队列;grunt.warn()可被--force 忽略,区别要背熟。 - grunt.task.clearQueue()
官方未公开但源码可见,可在任务失败回调里手动清空剩余队列,实现“秒退”。 - 并发任务与串行任务
国内常把grunt.registerTask('default', ['a', 'b', 'c'])写成串行,并发任务(concurrent) 失败时默认不会中断主队列,需要额外监听concurrent:done:fail事件。 - CI 场景
阿里、腾讯等厂内脚本统一加set -e,一旦 Grunt 返回非 0 即结束;候选人要说明本地如何模拟同样行为。
答案
在 Grunt 1.0 及以上版本,默认行为就是“任务失败立即中断后续串行队列”,无需额外配置;
若项目基于 0.4.x 或有人显式加了 --force,则按以下三步保证“失败即停”:
-
启动命令去掉容错开关
grunt default --no-force # 老版本必须加 -
每个任务内部统一使用
this.async()并正确回传状态grunt.registerTask('eslint', function() { var done = this.async(); grunt.util.spawn({ cmd: 'npx', args: ['eslint', 'src'] }, function(err) { if (err) { grunt.log.error('ESLint 未通过'); return done(false); // 标记失败,Grunt 会立即中断队列 } done(); }); }); -
若想在失败时额外做清理,可监听
grunt.event.on('fail')并手动grunt.task.clearQueue(),确保后续任务连日志都不打印。
一句话总结:“只要不用 --force,且任务内部正确回传失败状态,Grunt 会自动中断串行队列;老版本需显式 --no-force。”
拓展思考
- 如果任务链里混入了
grunt-concurrent,如何做到“子进程一失败,主进程秒退”?
答:在concurrent目标里加grunt.event.on('concurrent:fail', grunt.fail.fatal),把子进程失败事件提升到主进程 fatal 级别。 - 大型 Monorepo 中,不同子包用同一个 Gruntfile,如何只中断当前包而不影响其他包?
答:把每个包写成独立的grunt.task.run,外层用 Node 脚本循环调用,失败时通过process.exit(1)跳出循环,而不是在 Grunt 内部清队列。 - 如何在失败时把错误信息同步到企业微信或飞书?
答:在grunt.fail.registerReporter(function(msg){ axios.post(webhook, {text:msg}) }),利用官方 Reporter 机制,保证任何 fatal 都能推送到群,避免“本地成功、CI 失败”无人感知。