Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

You Don't Know JS 小筆記 - callback & promise & async #5

Open
marshal604 opened this issue Jun 3, 2020 · 0 comments
Open

You Don't Know JS 小筆記 - callback & promise & async #5

marshal604 opened this issue Jun 3, 2020 · 0 comments
Labels
Front-End article is about front-end

Comments

@marshal604
Copy link
Owner

marshal604 commented Jun 3, 2020

參考文章 You Don't Know JS: Async & Performance

關於callback

callback就是在函數執行完畢後,會執行的函數

function greeting(callback) {
	console.log('hello');
	callback();
}

greeting(function() {
	console.log('greeting completed')
});
// hello
// greeting completed

callback的缺點

  • 無法控制callback什麼時候執行,造成信任度低
  • 當過多的callback時,會因為程式碼跳來跳去造成可讀性差

無法控制callback什麼時候執行,造成信任度低

以吃飯邊看電視的例子來看,我希望吃東西時配電視看,可是因為沒辦法控制eatFinish什麼時候呼叫,所以變成吃飯 -> 吃飽 -> 看電視

function eat(callback) {
	console.log('Eating');
	callback();
}

function watchTv() {
	console.log('Watching TV');
}

eat(function eatFinish() {
	console.log('Eat Finish');
});
watchTv();
// Eating
// Eat Finish
// Watching TV

當過多的callback時,會因為程式碼跳來跳去造成可讀性差

這邊以煮泡麵打電腦完洗澡的例子來看,
我們煮開水 -> 煮泡麵 -> 玩遊戲 -> 關電腦 -> 洗澡,這個流程很正常,但是我們看以下程式碼時,是有點痛苦的,為什麼呢,因為每個動作都黏在一起,煮開水,去煮泡麵,可是要煮完泡麵才能玩遊戲,所以也放放在同一塊,而且強迫看code的人需要一個一個細心理解每行程式碼做到哪要做哪一步驟,這都會消耗蠻多心力,這種寫法也叫做callback hell

function boilWater(callback) {
	console.log('Boil Water');
	callback();
}

function playGame(callback) {
	console.log('Playing Game');
	callback();
}

function  takeTheBath() {
	console.log('Take the Bath');
}

function action() {
	boilWater(function cookInstantNoddles() {
		console.log('Cook Instant Noddles');
		playGame(function turnOffComputer() {
			console.log('Turn Off Computer');
			takeTheBath();
		});
	});
}

action();

關於promise

隨著時代演進,為了改善callback的問題,創造了Promise,以下用段故事說明Promise的行為

我走到肯德基櫃檯,點了雞米花,我藉由付款的行為(request),得到了叫號牌587,也就是等等會給我雞米花的承諾(Promise)

於是我愜意的拿著叫號牌去附近的書店晃晃看看書,看著看著覺得肚子餓了,就回到肯德基的櫃檯,也不管是不是過號了,跟店員說著,我的叫號牌587好了嘛,好了就給我雞米花吧,這時會有兩個常見的場景跟一個不常見的場景出現
- 常見
1. 店員雖然覺得我很慢才來拿,但看到叫號牌,還是拿了餐點給我 (成功)
2. 店員跟我道歉,原本以為還有雞米花,結果點完餐後才發現沒了 (失敗)
- 不常見
3. 我回去時,店員說電腦上沒有587的資料,是不是我走錯地方了 (無回應)

總得來說,至少在常見的情況下,我可以隨時掌控我何時要拿雞米花這件事,跟callback相比,callback就像是郵差一樣,我寄了信,他何時送達,有沒有送達成功,都可能需要第三個人跟我說,我根本沒辦法掌握我那封信的成功與否。

Promise改善了什麼

讓我們先回顧一下callback的缺點

  • 無法控制callback什麼時候執行,造成信任度低
    • Promise藉由上則故事可以知道,他將控制權反轉過來抓在手裡,且在常見的情況下,可以知道是成功還是失敗,並且只會是成功或失敗其中一中,不會兩個同時並存。
function checkWallet() { ... }
const order = new Promise((resolve, reject) => {
	if (checkWallet()) {
		resolve('付款成功');
	} else {
		reject('錢不夠');
	}
});

order
.then(() => {
	console.log('好吃'); // checkWallet回傳true就顯示好吃
})
.catch(() => {
	console.log('餓肚子') // checkWallet回傳false就顯示餓肚子
})
  • 當過多的callback時,會因為程式碼跳來跳去造成可讀性差
    • 在大部分的情況下promise的巢狀會比較少一點,有加強了可讀性,但不可否認,在一些情況下,並不會改善什麼。
// 這邊一樣以`煮開水` -> `煮泡麵` -> `玩遊戲` -> `關電腦` -> `洗澡`為例子
function boilWater() {
	console.log('Boil Water');
	return Promise.resolve();
}

function cookInstantNoddles() {
	console.log('Cook Instant Noddles');
  return Promise.resolve();
});

function playGame(callback) {
	console.log('Playing Game');
	return Promise.resolve();
}

function turnOffComputer() {
	console.log('Turn Off Computer');
	return Promise.resolve();
});

function  takeTheBath() {
	console.log('Take the Bath');
	return Promise.resolve();
}

function action() {
	// 是不是簡潔閱讀了許多了呢?
	boilWater()
	.then(() => cookInstantNoddles())
	.then(() => playGame())
	.then(() => turnOffComputer())
	.then(() => takeTheBath());
}

action();

關於async & await

隨著時代演進,callback的大部分缺點都被promise所改善了,但是當邏輯相當複雜時,promise的巢狀還是有那麼點閱讀上的缺憾,沒錯,async, await幫助我們解決了這個問題

async&await如何改善閱讀上的問題

我們將剛剛的煮泡麵再複雜化一點
煮開水 -> 拿兩顆蛋 -> 同時煮泡麵和水煮蛋 -> 加蛋到泡麵鍋和水煮蛋鍋裡 -> 加辣 -> 試吃 -> 加辣

function boilWater() {
	console.log('Boil Water');
	return Promise.resolve();
}

function takeEggs(count) {
	const eggs = [];
	for (let i = 0; i < count ; i++) {
		eggs.push('egg');
	}
	return eggs;
}

function cookInstantNoddles() {
	console.log('Cook Instant Noddles');
  return Promise.resolve();
});

function cookSoftBoiledEggs() {
	console.log('Cook Soft-Boiled Eggs');
  return Promise.resolve();
}

function addItemTo(item, where) {
	console.log(`Add ${item} to ${where}`);
	return Promise.resolve();
}

boilWater()
.then(() => {
	const eggs = takeEggs(2);
	const p1 = cookInstantNoddles()
	.then(() => addItemTo(eggs[0], 'first pot'));

	const p2 = cookSoftBoiledEggs()
	.then(() => addItemTo(eggs[1], 'second pot'));
	return Promise.all([p1, p2])
});

是不是有越來越複雜的傾向了呢,接著我們用async, await試著做一遍,是不是看起來順利的把code扁平化了,而且一路看下來不需要兜圈子了呢

try {
	await boilWater();
	const eggs = takeEggs(2);
	await cookInstantNoddles();
	await cookSoftBoiledEggs();
	await addItemTo(eggs[0], 'first pot');
	await addItemTo(eggs[1], 'second pot');
} catch(err) {
	console.log(err.msg);
}
@marshal604 marshal604 added the Front-End article is about front-end label Jun 3, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Front-End article is about front-end
Projects
None yet
Development

No branches or pull requests

1 participant