認識 Gulp.js — Gulp 任務的非同步特性

如何標示非同步任務已完成 (async completion)

Zong-Rong Huang
9 min readDec 25, 2022
https://github.com/gulpjs/artwork

概述

本文會介紹為何需要在 Gulp 任務中加入非同步完成的標示以及加入標示的方法。

同時透過實際經驗推敲出,基本任務和複合任務在加入非同步完成標示時有些許不同。

本文內容如下:

.什麼是非同步完成 (async completion)

.如何標示非同步任務已完成

.To return a stream or not to?

什麼是非同步完成 (async completion)?

在 Gulp 4 中,所有的 Gulp 任務皆是非同步任務。由於非同步特性,我們無法確切掌握某個任務是否已經執行完畢、是否能夠接著執行下一個相依任務。這樣一來,一連串任務執行下來,過程中可能發生錯誤或結果不如預期。

因此,Gulp 4 另外提供多種方式用來標示非同步的任務已完成 (async completion):當 Gulp 接收到標示後,就會安心進行下一個非同步任務。

如果忘了在任務中加上非同步完成的標示,Gulp 一樣會執行任務,只是執行結果變得無法掌握。

Gulp 也會在 terminal 中拋出警告訊息 “Did you forget to signal async completion?”,提醒開發者應加上非同步完成的標示。

如何標示非同步任務已完成

根據官方文件,有 7 種方式可以標示任務已完成:

  • 回傳 Node.js stream、event emitter 或 child process
  • 回傳 Rx.js 的 observable
  • 回傳 Promise
  • 傳入錯誤優先處理函式 (error-first callback)

以下介紹常見的方式:回傳 Node.js stream (推薦)、回傳 Promise 和傳入錯誤優先處理函式。

回傳 stream

在開頭加上 return 即可。

// gulpfile.js

const { dest, src } = require("gulp");

function task () {
return src(...)
.pipe(...)
.pipe(dest(...))
}

src 開始到 dest 結束,檔案資料都以 stream 格式傳遞和接受處理。因此,只要加上 return 就能回傳 stream。

Gulp 用到的外掛程式通常是對 stream 做處理,處理完後也會回傳 stream。因此,在任務函式中回傳 stream 來標示非同步完成,基本上都不會有問題。

回傳 Promise

// gulpfile.js

const { dest, src } = require("gulp");

function task () {
return Promise.resolve(
src(...)
.pipe(...)
.pipe(desc(...))
)
}

使用錯誤優先處理函式 (error-first callback)

如果不回傳上述類型的資料,必須提供錯誤優先處理函式作為任務函式的 argument。

錯誤優先處理函式通常以 cb 代表 (即 callback 縮寫)。cb() 內可不傳入任何值,也可以放入 console.log 印出提示。

// gulpfile.js

function task (cb) {
src(...)
.pipe(...)
.pipe(dest(...))

// 通知 Gulp 非同步任務已完成
cb()

// 通知 Gulp 非同步任務已完成 + 印出 console 提示
cb(console.log("Task done"))
}

不過,開發者不需要自行宣告錯誤優先函式。根據這一篇 StackOverflow,這個 callback 是由 Gulp 4 引用的 undertaker 套件提供:

The callback function comes from Orchestrator (or the new one — undertaker — in Gulp 4) and is actually nothing more than a call to tell the task system that your task is “done”.

什麼時候適合用到錯誤優先處理函式呢?根據同一篇 StackOverflow 的回答,如果該任務用到的外掛程式不會回傳 stream,適合傳入 callback。

By returning a stream, the task system is able to plan the execution of those streams. But sometimes, especially when you’re in callback hell or calling some streamless plugin, you aren’t able to return a stream. That’s what the callback is for. To let the task system know that you’re finished and to move on to the next call in the execution chain.

To return a stream or not to?

雖然知道如何在 Gulp 中標示非同步任務完成,也儘量使用回傳 stream 的方式,但在實際使用上還是不免碰到不少疑問:

  • 以上方式都可以任意使用嗎?
  • 某些任務只適合某些方式嗎?
  • 為什麼回傳了 stream,還是出現 “Did you forget to signal async completion?” 警告訊息或任務沒有成功執行?

自己摸索一番後推敲出基本任務和複合任務的非同步完成標示方式不太相同。

關鍵在於 seriesparallelsrc 還有 dest 都是函式,但有不同的回傳值。

基本任務

srcdest (包含 pipe) 都和 Node.js stream 有關,負責回傳 stream。用 console.log可以看到 stream 物件有極複雜的內容。

// gulpfile.js

const {src, dest} = require('gulp')

// 印出複雜的 stream 物件
console.log(src(...))
console.log(src(...).pipe(...))
console.log(src(...).pipe(...).pipe(dest(...)))

因此,要標示非同步的基本任務完成,可以回傳 stream,也可以用其他的方式:

// gulpfile.js

// OK
function task () {
return src(...)
.pipe(...)
.pipe(dest(...))
}

// OK
function task () {
return Promise.resolve(
src(...)
.pipe(...)
.pipe(dest(...))
)
}

// OK
function task (cb) {
src(...)
.pipe(...)
.pipe(dest(...))

cb()
}

複合任務

seriesparallel 是負責為任務建立排程的函式,執行後回傳 undefined 而非 stream。透過 console.log 印出函式則更容易了解:

// gulpfile.js

const {src, dest, series, parallel} = require('gulp')

function task1 () {...}
function task2 () {...}

// [Function: series] [Function: parallel]
// 函式已宣告但未執行
console.log(series(task1, task2), parallel(task1, task2))

// undefined undefined
// 函式已執行
console.log(series(task1, task2)(), parallel(task1, task2)())

從印出的資料中可以看出,seriesparallel 行為就像是一般沒有回傳值的 JavaScript 函式。

由於複合任務執行後不會回傳 stream,用回傳 stream 的方式標示則會出現警告訊息。用錯誤優先函式和 Promise 處理則會更適合。

// gulpfile.js

// BAD
// 任務沒有執行,出現警告訊息;只是回傳函式,但沒有執行
function task() {
return series(...)
}

// KINDA BAD
// 任務成功執行,出現警告訊息
function task () {
return series(...)()
}

// OK
function task() {
return Promise.resolve(
series(...)()
)
}

// OK
function task (cb) {
series(...)()
cb()
}

要注意的是,必須在 seriesparallel 函式後加上 (),才會執行裡面的基本任務。

結論

總結來說,基本任務可以用各種方式通知 Gulp 非同步任務已完成。

複合任務則無法使用回傳 stream 的方式。因為複合任務只是一般的函式,不會回傳 stream,因此不適合用回傳 stream 的方式標示。

--

--

Zong-Rong Huang

Frontend web developer/technical writer that writes to learn and self-entertain. I’m based in Taiwan.