這系列我要來介紹javascript裡面最令人頭痛的一個議題

Asynchronous (非同步)

然後一步步的看javascript在進化的過程中

是如何處理這個問題的

第一站 - Callback

相信常寫javascript的人一定對下面這種code不陌生

1
2
3
4
5
6
doSomething(function(err, result) {
if (err) {
// 處理error
}
// 處理result
});

把一個function當做參數(aka callback function)

傳到另外一個function裡

等到事情做完他就會去call那個callback function

用有點像是今天我請Gordon幫我送一個包裹給Stanley

然後我跟他說,call me back when you’re done

你事情做完了再來跟我講,啊我先繼續做我的事情

也就是javascript的non blocking的特性 射後不理(?

在做非同步的事情的時候通常都是這種模式

1
2
3
4
5
6
7
8
function doSomeAjax(url, callback) {
// 發http request
callback(result); // 做完了要呼叫callback傳回結果
}
doSomeAjax('http://some.url', function(result) {
// 拿到結果惹
});

Callback Hell(回呼地獄)

但是代誌不是憨人想的那麼簡單

如果今天要做的ajax很多,而且要有前一次的結果才能做下一個ajax

看起來會像這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
doSomeAjax(url1, function(result1) {
doSomeAjax(url2, function(result2) {
doSomeAjax(url3, function(result3) {
doSomeAjax(url5, function(result4) {
doSomeAjax(url6, function(result5) {
doSomeAjax(url7, function(result5) {
// WTF 我現在在第幾層
// 7嗎 不是喔
// 而且括號少打了你有發現嗎
});
});
});
});
});

也就是惡名昭彰的

Callback Hell (回呼地獄)

functception

你會迷失在多層縮排跟括號裡面

你白痴嗎,我可以把那些function分開寫啊
好啊你試試看啊ㄏㄏ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function doFirstAjax(callback) {
doSomeAjax(url1, doSecondAjax, callback);
}
function doSecondAjax(result, callback) {
doSomeAjax(url2, doThirdAjax, callback);
}
function doThirdAjax(result, callback) {
doSomeAjax(url3, callback);
}
doFirst(function(finalResult) {
// 這不是解決了嗎
});

乍看之下好像不錯

可是這樣一來每個function都跟他下一個要做的事綁在一起了

如果今天突然不需要某個ajax了

或是某個ajax需要多額外的參數

code會很難改的QQ

這樣把callback丟來丟去的

很容易會亂掉

callback

而且還會有一個很嚴重的問題

也是大家最討厭處理的

Error handling

blue screen

在多層callback裡

error是很難處理的

會搞到小頭爆掉

小可愛別擔心,Promise來惹

在同步的世界裡,一個東西的值是可以傳來傳去的

1
2
3
var name = 'Gordon';
var length = 18;
var isHandsome = lookatTheMirrow(name); // true

但是在async的世界裡我們要怎麼表達呢

這個時候promise就出現了

1
var promise = doSomeAjax(url);

這個promise就可以拿來代表這個ajax

而且也可以跟一般的變數一樣丟來丟去

A Stateful Machine

promise用的是一種狀態機的方式來表達一件事

主要有三種狀態: pending, fulfilled, rejected

一個promise剛建立的時候是pending

他有可能做完事拿的result變成fulfilled狀態

也有可能有error變成rejected的狀態

我們可以用.then()的方式拿到他的result

.catch()抓到他的error

1
2
3
4
5
6
7
doSomethingAsync()
.then(function (result) {
// ...
})
.catch(function (err) {
// ...
});

不對啊這跟callback hell有什麼關係

Promise好用的點就在於

他的then()catch()都會回傳一個Promise

也就是說他們可以串在一起!!!

1
2
3
4
5
6
7
8
9
10
11
12
doSomethingAsync()
.then(function (result) {
// ...
})
.then(function (result) {
// ...
})
.then(function (result) {
// ...
})
.catch(function (result) {
});

callback hell的多層縮排瞬間變成一層縮排

而且error變的更好處理了

上面任何個then出錯了

都會直接進到最後面那個catch

那要怎麼使用Promise呢

Promise現在已經成為下一代javascript(es6)的標準了

在不支援的環境也有許多library實做了promise可以用

那要怎麼開始呢

1
2
3
4
5
6
7
8
9
10
11
12
var promise = new Promise(function(resolve, reject) {
// do something, possibly async
if (/* everythings fine */) {
resolve(result);
} else {
reject(err);
}
});
promise.then(function(result) {
// 拿到result了
});

這樣就可以把一個原本不是promise的東西變成Promise

也可以直接一個Promise.resolve弄出一個fulfilled的Promise

1
2
3
4
5
var promise = Promise.resovle(1);
promise.then(function(result) {
// result == 1
});

而在串連的過程

所有的return值都可以往後傳

1
2
3
4
5
promise.then(function() {
return 1;
}).then(function(result) {
// result == 1
});

待續

從callback到Promise是對javascript來說是一個不錯的進展

但是你這樣就被fulfilled了嗎

下集待續ㄏㄏ

參考資料