The Call Stack(呼叫堆疊)
我們今天有一個function,它需要好幾個函式一齊運作才能完成。而呼叫堆疊的作用就是幫Javascript跟蹤函數的位置,以及當函式完成任務後,讓值返回正確的位置。
像以下範例,要完成rightTriangle(3, 4, 5)
,就必須先知道rightTriangle()
的計算方式,然後再把每個square()
的值算出來。
function square(x){
return x * x
}
function rightTriangle(a, b, c){
return square(a) + square(b) === square(c)
}
rightTriangle(3, 4, 5)
以下是用Loupe網站展示出來的call stack:
Sync/Async(同步與非同步)
用setTimeOut()
的例子來說,我們今天想執行的順序是:
Good morning => Good afternoon => Good night
console.log('Good morning')
setTimeout(() => {
console.log('Good afternoon')
}, 1000)
console.log('Good night')
但我們跑出的結果會是:
這是因為setTimeout()
在1000毫秒後才被印出,所以順序會是:
Good morning => Good night => Good afternoon
這種狀況就是非同步(Async),browser接收到setTimeout()
的需求後,不用等setTimeout()
印出後才執行其他程式。
那如果我們想要按Good morning => Good afternoon => Good night
的順序印出要怎麼做呢?
一般來說可以用callback,但當太多function被nested,就可能造成callback hell。還好現在有Promise
與async/await
可以解決這個問題!下面會詳細講到。
Promise
Promise
是ES6新增的物件,可以幫忙解決非同步(asynchronous)以及callback hell
的問題:
簡單的promise寫法:
- 運用then連結:如果正確完成第一個promise後,回傳一個新的promise,如果正確完成第二個promise後,回傳另一個新promise....
- catch捕獲錯誤:只要有任一個promise拒絕,就會run寫在catch裡的指令。
function promise() {
return new Promise((resolve, reject) => {
resolve(value);
reject(reason);
})
}
promise()
.then((value) => {
//do somthing
return promise()
})
.then((value) => {
//do somthing
return promise()
})
.catch(reason) {
//do something when wrong}
Promise的狀態
總共有三種:
- pending //執行中
- fulfilled //已實現
- rejecttion //已拒絕
Promise 方法
- Promise.all(iterable)
Promise.all方法是說,當所有promise都回傳resolve後,才進入下一階段的任務,只要有任一promise被reject就不會進入下一階段。iterable裡放陣列。 - Promise.race(iterable)
只要有任一個promise回傳resolve,就進入下一階段任務,其餘都忽略。如果全部都被reject則視為失敗。 - Promise.resolve
Promise.resolve(value)方法回傳一個以value參數判定結果的Promise物件。 - Promise.reject
Promise.reject(reason)方法返回一個Promise物件,該物件由於給定的原因而被拒絕。
Promise 範例
範例1:想要讓程式按照一定的順序執行
想要讓backgroundColor的顏色按照,紅、橙、黃、綠依次進行。
let colorChange = (color, time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
document.body.style.backgroundColor = color;
resolve();
}, time)
})
}
colorChange('red', 1000)
.then(() => colorChange('orange', 1000))
.then(() => colorChange('yellow', 1000))
.then(() => colorChange('green', 1000))
範例2:模擬請求資料的狀況
用setTimeout與random模擬連結資料的時間。
function request(url) {
return new Promise((resolve, reject) => {
let delay = Math.floor(Math.random() * 5000) + 3000;
setTimeout(() => {
if (delay < 5000) {
resolve(`Here is your data from ${url}`);
} else reject(`Connecting ERROR`);
}, delay)
})
}
request(`blog/api/page1`)
.then((data) => {
console.log(`blog/api/page1`)
return request(`blog/api/page2`)
})
.then(() => {
console.log(`blog/api/page2`)
return request(`blog/api/page3`)
})
.then(() => {
console.log(`blog/api/page3`)
return request(`blog/api/page4`)
})
.then(() => {
console.log(`blog/api/page4`)
})
.catch(() => {
console.log(`A REQUEST ERROR!!!!`)
})
可將程式碼貼到console測試結果看看。
async / await
async/await
函式簡單來說就是promise的簡單寫法。
簡單的async/await寫法:
- async加在前面function
- await一定要寫在async function裡
- async function跑完會回傳一個promise,後面可以接then繼續連結
async function doSomething() { //創立async function
await a(); //等a跑完,才執行b
await b(); //b執行完畢
}
doSomething(); //呼叫上面的函式
doSomething().then(() => {}) //doSomething()跑完後,後面可以繼續接其他指令
像上述promise的範例1,使用async/await
語法就會變成這樣:
async function rainbow() {
await colorChange('red', 1000);
await colorChange('yellow', 1000)
await colorChange('orange', 1000)
await colorChange('green', 1000)
}
rainbow();
如果想在跑完背景顏色後印出DONE!!,可以這麼寫:
//用then連結
rainbow().then(() => console.log(`DONE!`))
//或是再用一個async函式
async function rainbowDone() {
await rainbow();
console.log(`DONE!`)
}
rainbowDone();
處理 Error
那我們要如何抓取async函式中的Error呢?通常有兩種基本的方法:
- 使用try...catch
- 使用.catch
//使用try...catch
async function request() {
try {
let response = await fetch('http://blog/api/edit');
} catch(err) {
console.log(err); // TypeError: failed to fetch
}
}
request();
//使用.catch
async function request() {
let response = await fetch('http://blog/api/edit');
}
request().catch(console.log) // TypeError: failed to fetch
用promise的範例2來重寫成async函式:
//使用try...catch抓取error
async function res(){
try{ await request(`blog/api/page1`)
await console.log(`blog/api/page1`)
await request(`blog/api/page2`)
await console.log(`blog/api/page2`)
await request(`blog/api/page3`)
await console.log(`blog/api/page3`)
await request(`blog/api/page4`)}
catch(err){
console.log(err)
}
}
//使用.catch抓取error
async function res(){
await request(`blog/api/page1`)
await console.log(`blog/api/page1`)
await request(`blog/api/page2`)
await console.log(`blog/api/page2`)
await request(`blog/api/page3`)
await console.log(`blog/api/page3`)
await request(`blog/api/page4`)}
res().catch(console);
通常來說當我們使用async/await後,我們會改用常規的try..catch。但當我們不在async函數之內,就無法使用await語法,所以這時通常是添加.then/catch來處理結果或掉出來的error。
Conditional ternary operator
Conditional ternary operator
又稱三元運算式,是透過:
(冒號)?
(問號)兩種運算子來簡化if
語句使用。
語法
condition ? exprIfTrue : exprIfFalse
範例1:簡單示範
如果age
大於等於18,則函式回傳Beer,反之則回傳Water
function beverage() {
let age = Math.floor(Math.random() * 30) + 1;
return (age >= 18) ? 'Beer' : 'Water';
}
範例2:if..else串接
function ticketPrice() {
let age = Math.floor(Math.random() * 100) + 1;
console.log(age);
return (age <= 6) ? '$5' :
(18 > age && age > 6) ? '$10' :
(65 > age && age > 17) ? '$15' :
'$5';
}
// 等同於:
function ticketPrice(){
if (age <= 6) { return '$5'; }
else if (18 > age && age > 6) { return '$10'; }
else if (65 > age && age > 17) { return '$15'; }
else { return '$5'; }
}
參考資料: