進階 Javasctipt 概念 (7)

PY
9 min readApr 18, 2019

--

模組化與例外處理

之前使用 webpack 時,整理了一下 webpack 與 module 的概念,所以在開始之前我想先來複習一下

所以我們為什麼要 webpack 與模組化,在 web browser 中,JS 是全域的,這代表你引用的每個檔案都會汙染到其他檔案的 scope,與其他語言不一樣的是其他語言跑的是伺服器,可以自己創造自己的開發環境,但是在瀏覽器上面,你無法跑自己的開發環境,這時你就得使用 nodejs,一個 server 端的開發環境來分隔每個檔案,模組化並且在最後用 webpack 打包成瀏覽器使用的檔案

Module Pattern

首先來看看我們的 scope 層級,從上到下分別為

  1. Global
  2. Module
  3. Function
  4. Block

module 模組可以輸出下面兩個層級的 scope,Function 與 Block

我們可以用 IIFE 或閉包來把一個 scope 包起來

var fightModule = (function() {
var harry = 'potter'
var voldmore = 'voldmore'
function fight(char1,char2){
var atk = Math.floor(Math.random() * char1.length)
var atk2 = Math.floor(Math.random() * char2.length) return atk > atk2 ? char1 + 'wins' : char2 + 'wins'
}
return {
fight: fight
}
})()

我們可以用 fightModule.fight 來調用這個 scope 內的方法,這是最原始的 module 方式

CommonJS, AMD, ES6

雖然在最上面貼的文章有介紹到,但還是重新認識一次這三種 module 方式

  1. commonJS
var module1 = require('module1')
var module2 = require('module2')

首先 require 是同步的,所以如果你引用的模組很大的話就得 load 很久,這裡來介紹一下 browserify ,他也是個打包工具,是基於 commonJS 來做打包的,當然跟 webpack 一樣有著 Dependencies tree

2. AMD

define(‘MouseCounterModule’, [‘jQuery’], $ => {
let numClicks = 0;

const handleClick = () =>
console.log(++numClicks);

return {
countClick: () =>
$(document).on(‘click’, handleClick);
};
});

用來解決 require 的同步問題,可以在 load 時不用等到前面的模組 load 完才能 load

3. ES6

import {moduleA} from ‘./a.js’
export default () => {}

ES6 Module 出現了,我們可以不用管什麼載入同步非同步了,一律非同步,而且 import from 語法簡潔易讀,與 AMD 簡直是天壤之別, export default 輸出整個檔案 module ,而 export function 也能讓同個檔案中輸出不同的 module,非常好用

另外在 browser 中也可以在 script 內使用 type=”module” ,就能直接在 browser 用 module 了,但會有 cors 問題得在同個 server 下才能使用喔!

<!DOCTYPE html>
<html>
<head>
<title> JS </title>
</head>
<body>
<script type="module">
import {add} from './libA.js';
console.log(add(10,20));
</script>
</body>
</html>

另外這個 type = ”module” 是 defer 的,所以你會等整個葉面都下載及分析完成後才會執行,相當於把 js 放在頁尾,偷渡一下 async 是在載完之後先執行但也會同時繼續載入頁面與其他 js,加在一起就是等載入完就執行並以非同步執行其他 js

<script defer src="/path/to/your.js"></scrpt>
<script async src="/path/to/your.js"></scrpt>
<script async defer src="/path/to/your.js"></scrpt>

Error Handling

如何處理好 exception, Error Message 是成為一個好的程式設計師的必經之路,而前端 JS 也比較少提起這部分,所以讓我們來理解一下 JS 是如何對這些錯誤做處理

Throw Error

我們可以用 Error 建構子來創造錯誤物件然後用 Throw 拋出物件

var error = new Error("error message");
console.log(error) // Error: error message
throw error

Try Catch Finally

各種語言好比 JAVA 都有 try catch 的機制,JS 也不例外,我們可以用 try catch 來將執行發生的錯誤抓到 catch 中,throw 關鍵字能夠拋出錯誤訊息,最後是 finally 能夠在程式跑完沒有錯誤時執行

function fail() {try {console.log('this works');throw new Error('oopsie');} catch(e) {console.log('error', e);} finally {console.log('still good');return 'returning from fail';}console.log('never going to get here'); // not reachable}fail();

但有個問題是錯誤處理是同步的,當我們執行 try catch 時,放在 call stack 的 function 依舊會直到 try catch 執行完後才執行

try {
setTimeout(function() {
fakevar // undefined
},1000)
}

非同步錯誤處理

我們可以在使用 Promise 來包裝 Async Error Handling

Promise.resolve('asyncfail').then(response => {console.log(response)throw new Error('#1 fail')}).then(response => {console.log(response)}).catch(err => {console.error('error', err.message)}).then(response => {console.log('hi am I still needed?', response)return 'done'}).catch(err => {console.error(err)return 'failed'})

在 Promise 中我們可以在鏈式調用中用 catch 來捕獲鏈式的 error 或是 reject,那如果是 async await 呢?

(async function() {try {await Promise.reject('oopsie')} catch (err) {console.error(err)}console.log('This is still good!')})()

我們可以用 try catch 把 await 包起來,再用 reject 取代 throw 拋出錯誤,注意的是如果使用兩層以上的 Promise ,內層的 Promise 會冒泡,將外層的 promise 也 reject 掉,所以我們得在裡面那層也需要做 catch

Promise.resolve('asyncfail').then(response => {Promise.resolve().then(() =>{     throw new Error('fail')}.catch()return 5 console.log(response)throw new Error('#1 fail')}).catch()

還有要注意的是如果在 .then 中有錯誤,例如說存取 undefined 的物件,會直接 reject 到最後的 error

Class & Error Handling

我們可以用 es6 類別來封裝每個回傳的錯誤處理物件,利用繼承錯誤物件,在 Error 物件中有的 message 屬性 就能封裝要回傳的訊息

class authenticationError extends Error {constructor(message) {super(message)this.name = 'ValidationError'this.message = message}}class PermissionError extends Error {constructor(message) {super(message)this.name = 'PermissionError'this.message = messagethis.favouriteSnack = 'grapes'}}class DatabaseError extends Error {constructor(message) {super(message)this.name = 'DatabaseError'this.message = message}}throw new PermissionError('A permission error')

總結

我們從 ERROR 出發,經過每層的 catch ,試著讓所有的錯誤發生時都能被捕獲到,在撰寫測試程式時也更好寫,所以好的錯誤處理能讓程式更加穩固,維護性更高

--

--