越來越發(fā)現(xiàn)JavaScript這個語言相當?shù)牟诲e,之前還一直以為就跟HTML,CSS一樣差不多(實際上這兩者的門道也不淺)玄组。隨著AJAX滔驾,ES6,甚至ES7新標準新特性俄讹,很多玩法加入進來哆致,再加上node的推動下JavaScript的生態(tài)也越來越好,多學習一下人很有裨益患膛。
(題外插一句摊阀,這個簡書貌似沒有支持MarkDown,光靠編輯器這幾個功能可能支持得不夠踪蹬,還有就是代碼段也沒有嗎胞此。)
1 最原始的異步編程
其實就是搞一個嵌套一類的東西,非常的oldschool跃捣,用setTimeOut來舉例子(工作中可以將其替換成ajax網(wǎng)絡(luò)請求漱牵,獲取到結(jié)果執(zhí)行下一步的)
比如弄一個簡單的例子,一方面來證明是異步的疚漆,復制到瀏覽器的conclose里即可酣胀。
```javascript
function a (){
console.log("我是a我不是異步代碼")
setTimeout(
function(){console.log("我是a的異步代碼")}
,1000)
}
function b (){
console.log("我是b我不是異步代碼")
setTimeout(
function(){console.log("我是b的異步代碼")}
,1000)
}
a()
b()
```
看到a異步代碼永遠是在b的非異步代碼后面執(zhí)行,無論你怎么設(shè)置延時的定時器時間哪怕是設(shè)置為0娶聘,而且b的異步代碼也不會跑在b的前面闻镶。同樣作為異步代碼他會在滿足條件時才會觸發(fā),但是問題來了我要是好幾層怎么辦丸升,A依賴于B的請求返回結(jié)果铆农,B依賴于C的請求返回結(jié)果。发钝。顿涣。。酝豪,很可能是下面的情況
```javascript
setTimeout(
? () => {
? ? console.log("ZP很帥 +"+ 1);
? ? setTimeout(
? ? ? () => {
? ? ? ? console.log("ZP很帥 +" +2);
? ? ? ? setTimeout(
? ? ? ? ? () => {
? ? ? ? ? ? console.log("ZP很帥 +"+ 3);
? ? ? ? ? },
? ? ? ? ? 1000
? ? ? ? );
? ? ? },
? ? ? 1000
? ? );
? },
? 1000
)
```
但是這個東西看起來就知道不夠優(yōu)雅涛碑,怎么樣寫得更簡潔呢,可以試著分離一下孵淘。我在別的大佬的JS里經(jīng)常會遇到各種XXXXCallBack蒲障,借鑒一下人家的分離思想揉阎。
```javascript
var run = (steps, callback) => {
? setTimeout(
? ? () => {
? ? ? console.log("張鵬真的很帥 +" +steps);
? ? ? callback();
? ? },
? ? 1000
? );
};
run(1, () => {
? run(2, () => {
? ? run(3, () => {});
? });
});
```
其實已經(jīng)很不錯了,因為在JS里面函數(shù)也可以作為參數(shù)傳參進來這個特性烙如,剛開始這個我這個彎沒轉(zhuǎn)過來浪費了不少時間。
2 ES6新特性 promise
其實做到分離徘溢,還是會出現(xiàn)嵌套的問題對于這種嵌套的代碼還有個稱呼,“厄運金字塔”(其實挺貼切的想要得到你的結(jié)果從塔頂?shù)剿馐┟郏@種代碼改起來最蛋疼七扭八歪的翻默。)
于是ES6提供了個原生支持叫做promise來解決,他的構(gòu)造方法接受一個函數(shù),并且這個函數(shù)接受resolve和reject這兩個參數(shù)蹦渣。前者未來成功時調(diào)用后者未來失敗時調(diào)用(其實第一個厄運金字塔代碼類型callBack也可以分為成功的successCallBack和失敗的failCallBack于是代碼更難改。)
```javascript
var run = steps =>
? () =>
? ? new Promise((resolve, reject) => {
? ? ? setTimeout(
? ? ? ? () => {
? ? ? ? ? console.log(steps);
? ? ? ? ? resolve(); // 一秒后的未來執(zhí)行成功,需要調(diào)用
? ? ? ? },
? ? ? ? 1000
? ? ? );
? ? });
Promise.resolve()
? .then(run(1))
? .then(run(2))
? .then(run(3));
```
當然前半段代碼看不明白也沒關(guān)系,箭頭函數(shù) =>這個讓代碼變簡潔是個不錯的東西,當然也提高了閱讀的門檻袱箱,下面是我從MDN上找到
```javascript
(參數(shù)1, 參數(shù)2, …, 參數(shù)N) => { 函數(shù)聲明 }
//相當于:(參數(shù)1, 參數(shù)2, …, 參數(shù)N) =>{ return 表達式; }
(參數(shù)1, 參數(shù)2, …, 參數(shù)N) => 表達式(單一)
// 當只有一個參數(shù)時凉翻,圓括號是可選的:
(單一參數(shù)) => {函數(shù)聲明}
單一參數(shù) => {函數(shù)聲明}
// 沒有參數(shù)的函數(shù)應該寫成一對圓括號前计。
() => {函數(shù)聲明}
```
我的改寫可能讓你大概了解一點,異步函數(shù)有好處也有壞處伶棒,但是它確實能讓代碼簡短骇钦。
```javascript
var run = function run (steps){
new Primise(function(resolve,reject){
setTimeout({
console.log(steps);
resolve(function(steps));
},1000)
})
}?
Promise.resolve()
? .then(run(1))
? .then(run(2))
? .then(run(3));
```
是不是就沒有嵌套了,最起碼第一眼看上去很像同步代碼寇蚊,直觀的鏈式調(diào)用。
3 ES7的await和async語法糖
async標記函數(shù)為異步函數(shù),await表示他后面要返回為一個Promise對象,在這個promise的異步結(jié)果出來之后就再繼續(xù)往下執(zhí)行掂墓。
```javascript
var run = async steps => {
? await wait(1000);
? console.log(steps);
}
```
4 用生成器實現(xiàn)協(xié)程
協(xié)程是在線程里面川慌,所以是共享的線程資源,甚至連線程間切換消耗資源都省了。協(xié)程 Coroutine,一種協(xié)作多任務(wù)式多任務(wù)的亮靴,任務(wù)掛起與恢復實現(xiàn)任務(wù)間切換。
知道go的人肯定對協(xié)程有聽過(然而我目前也只是聽過于置,看過資料了解過的程度茧吊。)JavaScript是怎么實現(xiàn)協(xié)程,并沒有什么關(guān)鍵詞八毯。于是要通過生成器Generator來實現(xiàn)這個玩意搓侄,在JavaScript的“可迭代協(xié)議”和“迭代器協(xié)議”,function*和yield關(guān)鍵字配合下就完成了话速。
function*是生成生成器對象的讶踪,yield是用來在生成器的next()方法執(zhí)行時中斷位置返回右側(cè)表達式的值。
```javascript
function* IdGenerator() {
? let index = 1;
? while (true)
? ? yield index++;
}
var idGenerator = IdGenerator();
console.log(idGenerator.next());
console.log(idGenerator.next());
```
5 異步異常
其實異步代碼異常是一個問題泊交,現(xiàn)實中涉及到請求的尤其是這樣很可能是兩套邏輯再嵌套的那種乳讥。
原始的兩種callback,promise有resolve和reject兩種,await廓俭,async也有對應的將結(jié)果解析出來云石,生成器沒怎么細了解過,但是MDN上解釋還挺清楚的研乒。
熊四火老師的專欄關(guān)于這個異步寫的比較好建議多看幾遍汹忠。