Promise Context Switch 之 Drone Plugin的愛恨情仇

最近在使用Drone的時候有用到同事寫的JavaScript Plugin
原本是想說利用Drone Secret就可以藏好專案敏感的訊息, 所以又自己改了一版有Secret的Plugin

但是遇到了一個問題, 就是這個套件在Rancher deploy失敗的時候並不會在Pipeline顯示Failed.

Drone pipeline要失敗主要是偵測這個Container是否是exit in 0以外的Code(非正常結束)
如果Container裡面的Process exit in 1, 那這個Container也會exit in 1, Pipeline也就會失敗

原本想的很簡單
只要在返回錯誤訊息的時候加process.exit(1)就好了
這是原本的Code:

1
2
3
4
5
6
const workloadInfo = await axios.get(PLUGIN_PROJECT_API, {
auth: {
username: RANCHER_ACCESS_KEY,
password: RANCHER_SECRET_KEY
}
})

加了process.exit

1
2
3
4
5
6
7
8
9
10
11
const workloadInfo = await axios.get(PLUGIN_PROJECT_API, {
auth: {
username: RANCHER_ACCESS_KEY,
password: RANCHER_SECRET_KEY
}
}).then(response => {
console.log(response)
}).catch(error => {
console.log(error)
process.exit(1)
})

原本以為這樣就結束,打完收工.

但是歹擠沒有憨人所想的這麼簡單
pipeline還是return success
只是log會顯示process exit code 1
於是我試著拋出error

1
2
3
4
5
6
7
8
9
10
11
const workloadInfo = await axios.get(PLUGIN_PROJECT_API, {
auth: {
username: RANCHER_ACCESS_KEY,
password: RANCHER_SECRET_KEY
}
}).then(response => {
console.log(response)
}).catch(error => {
console.log(error)
throw new Error("Exit in code 1")
})

原本以為終於可以結束,打完收工.

但是事實證明憨人就是憨人
於是我想這應該是Thread(執行緒)的問題,於是就去查NodeJs到底是不是單執行緒
被我翻出了這一篇蠻有教育意義的文章

然後我就在想,嗯嗯應該是因為axios在http call的時候啟動了I/O thread的關係吧!
所以到Catch的時候已經是另外一個Thread, main thread已經結束了, 也難怪pipeline沒辦法exit in code 1

那這樣也沒辦法,只能用別的語言來改寫這個Plugin了
(事後證明我真的是個憨人XD)

這是用Golang改寫過後的Plugin,
還因此開啟我的Golang學習之路…

套件本身是可以用的,也會在deploy失敗時正常顯示失敗,因為Proccess 確實 exit in code 1
原本以為事情就這麼結束了
但我還是覺得這個疑問沒有得到回答
所以我就跑去問我的大學大神同學@David Kuo大大
然後也在StackOverflow上找到這篇文章
這才知道,原來在Promise的Catch裡面不管丟出什麼錯誤或exit process code in 1
JavaScript預設你已經知道這邊可能會有錯而且你會好好處理它

照著上面文章的解法改寫了一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
const workloadInfo = await axios.get(PLUGIN_PROJECT_API, {
auth: {
username: RANCHER_ACCESS_KEY,
password: RANCHER_SECRET_KEY
}
}).then(response => {
console.log(response)
}).catch(error => {
console.log(error)
setTimeout(() => {
throw new Error("exited in code 1") // process.exit(1) 也可以
})
})

Drone pipeline就正常Failed了!真是可喜可賀!

其實官方網站有寫到這是一個Catch的Trap, 詳見此
但是官方網站沒有寫到為啥這個可以Work, 但是StackOverflow那篇文章的解答有解釋到

在你setTimeout叫起另外一個function的時候,JavaScript就已經進入了一個新的Event Loop了
兩個function的context不同,也就不會預設你會好好處理這個Error
所以當你在function裡面拋出例外時,程式就會異常結束了

然而這邊我又有個疑問
如果是這樣寫呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 const workloadInfo = await axios.get(PLUGIN_PROJECT_API, {
auth: {
username: RANCHER_ACCESS_KEY,
password: RANCHER_SECRET_KEY
}
}).then(response => {
console.log(response)
}).catch(error => {
console.log(error)
throwError()
})

function throwError() {
throw new Error("exited in code 1")
}

這邊可以參考這位大大的文章
以及官方說明

因為setTimeout()是非同步事件,為了防止這個事件耗時太久,所以JavaScript會把這些有非同步Callback的函式都丟進去Event Queue裡,等到目前手上的任務都執行完再執行

而上面寫的throwError並不是非同步的函式, JavaScript並不會為了這個函式進入新的Event loop, Error也就會被好好處理了。

打完收工。