技術不難,困難的是要解決的問題是甚麼
非同步舉例:網路連線、排程、setTimeout、node.js 跟資料庫互動的程式、檔案存取/讀取、資料庫連線
非同步程式,不易做流程控制(那些先執行那些後執行)。
解決方式:
一、call back
二、promise 物件
三、Async Await
setTimeout 會延遲一段時間再跑裡面的函式,時間單位為千分之一秒,寫 2000 表示 2 秒鐘。
想做到的效果 : 延遲兩秒做加法,並將結果印出來。但印出的卻是 undefined。
因為這是非同步的程式,所以設定排程後不會原地等待兩秒,他會往下走然後回傳
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>非同步</title>
<script>
// 問題起源:非同步程式
function delayedAdd(n1, n2, delayTime) {
window.setTimeout(function() {
return n1 + n2
}, delayTime)
}
function test() {
let result = delayedAdd(3, 4, 2000)
console.log(result)
}
</script>
</head>
<body>
<h3>非同步控制流程</h3>
<button onclick="test()" style="padding: 10px 30px;">test</button>
</body>
</html>
點按鈕 => 1 先印出然後印出 undefined 兩秒後再印出 2 => 需求失敗
return 失效,因為寫在 function 內,他是 return function 不是 return delayedAdd
function delayedAdd(n1, n2, delayTime) {
// 設定排程,延遲一段時間後執行
window.setTimeout(function() {
console.log(2)
return n1 + n2
}, delayTime)
console.log(1)
}
function test() {
let result = delayedAdd(3, 4, 2000)
console.log(result)
}
一、cb
// cb function
function delayedAdd(n1, n2, delayTime, cb) {
// 設定排程,延遲一段時間後執行
window.setTimeout(function() {
// 延遲一段時間後,計算加法,呼叫 cb 函式
cb(n1 + n2)
}, delayTime)
}
function test() {
// 這樣寫表示從 delayedAdd 回傳值抓資料 delayedAdd(3, 4, 2000) 但回傳值只會拿到 undefined
delayedAdd(3, 4, 2000, function(result) {
// 函式會傳到 cb 參數
console.log(result)
})
}
二、promise
瀏覽器承諾幫你做一個工作,這個工作是延遲兩秒做加法,做完加法告訴人家我做完了並把結果丟進去,如果工作出狀況,就呼叫 reject
先放掉疑問,看流程 !! 非同步程式不等待
- 點按鈕呼叫 test()
- 呼叫 delayedAdd(3, 4, 2000)
- 建立新的 promise 物件 new Promise( (resolve, reject) => {略})
- 執行 promise 的工作交給另外的執行區,瀏覽器會請另外的 cpu 做裡面的工作
- 回傳 promise 物件
- promise.then(函式),函式接收兩秒後工作完成呼叫的 resolve()
// promise 物件
function delayedAdd(n1, n2, delayTime) {
// 建立 promise 物件: new Promise(執行函式),將要做的工作放入執行函式中
// 執行函式裡面會有兩個參數 resolve reject,第一個參數表示成功之後要呼叫的函式,第二個參數表示執行過程中遇到的任何問題會對應到 catch
let p = new Promise( (resolve, reject) => {
window.setTimeout(function() {
resolve(n1 + n2) // 工作完成,呼叫 resolve 函式,並把結果透過參數傳遞進去
}, delayTime)
})
return p // 傳遞 promise 物件。建立工作後立刻回傳,裡面的工作還在等待不管他
}
function test() {
let promise = delayedAdd(3, 4, 2000) // 拿到 promise 物件
promise.then(result => console.log(result)) // 用 .then 拿結果,結果是工作區 resolve 函式過來的
}
簡化上述代碼:建立完 promise 物件直接 return
function delayedAdd(n1, n2, delayTime) {
// 建立完 promise 物件直接 return
return new Promise( (resolve, reject) => {
window.setTimeout(function() {
resolve(n1 + n2) // 工作完成,呼叫 resolve 函式,並把結果透過參數傳遞進去
}, delayTime)
})
}
resolve 的呼叫會對應到 .then()
reject 的呼叫會對應到 .catch(),會用他做錯誤處理。例如:網路連線斷掉,就用 catch 做錯誤的狀況。
function delayedAdd(n1, n2, delayTime) {
// 建立完 promise 物件直接 return
return new Promise( (resolve, reject) => {
window.setTimeout(function() {
reject(n1 + n2) // 工作完成,呼叫 resolve 函式,並把結果透過參數傳遞進去
}, delayTime)
})
}
function test() {
let promise = delayedAdd(3, 4, 2000) // 拿到 promise 物件
promise.then(result => console.log(result)) // 用 .then 拿結果,結果是工作區 resolve 函式過來的
.catch(error => console.log('error', error))
}
先不要想為甚麼要這樣,直接看他怎麼運作。等練習熟練再看為甚麼
三、async await
前提:await 函式必須要 return promise 物件
真正改善部分是在呼叫的地方。原本是將工作區的結果,透過 reslove() 傳到 .then。現在,透過語法糖讓他閱讀上更容易。
await 一個回傳 promise 的函式,如果 awiat 後面接的不是 promise 他就是一個錯誤的語法。在函式中使用 await,必須在函式外面宣告 async。
錯誤處理:try catch
// async / await:簡化 promise 語法 (背後運作邏輯同 promise )
function delayedAdd(n1, n2, delayTime) {
// 建立完 promise 物件直接 return
return new Promise( (resolve, reject) => {
window.setTimeout(function() {
resolve(n1 + n2) // 工作完成,呼叫 resolve 函式,並把結果透過參數傳遞進去
}, delayTime)
})
}
async function test() {
let result = await delayedAdd(3, 4, 2000) // await 一個 promise,resolve 的結果會進入 result 裡面。在 await 的地方等待,等 result 回來。不管後面寫甚麼都會在這邊被卡住
console.log(result)
console.log('hi')
}
兩次網路連線抓不同資源、兩次延遲的加法,再用這個結果做其他事情。要怎麼辦呢 ?
另一個需求
延遲兩秒做 3 + 4 延遲三秒做 2 + 3,等到加法都做完再相乘 => 7 * 5
// 卡住了
function test() {
let promise1 = delayedAdd(3, 4, 2000)
let promise2 = delayedAdd(2, 3, 3000)
promise1.then(result => console.log(result))
promise2.then(result => console.log(result))
}
Promise.all([所有希望完成的 promise 都放入這個陣列]),
function test() {
let promise1 = delayedAdd(3, 4, 2000)
let promise2 = delayedAdd(2, 3, 3000)
// 多個 promise 都完成後才繼續工作
Promise.all([promise1, promise2]).then(function(results) {
console.log(results) // 會將所有的結果都送出 => [7, 5]
})
}
function test() {
let promise1 = delayedAdd(3, 4, 2000)
let promise2 = delayedAdd(2, 3, 3000)
// 多個 promise 都完成後才繼續工作
Promise.all([promise1, promise2]).then(function(results) {
let answer = results.reduce(function(total, value) {
return total * value
})
console.log(answer) // 35
})
}
同樣的邏輯用 async await 完成
// async / await:簡化 promise 語法 (背後運作邏輯同 promise )
function delayedAdd(n1, n2, delayTime) {
// 建立完 promise 物件直接 return
return new Promise( (resolve, reject) => {
window.setTimeout(function() {
resolve(n1 + n2) // 工作完成,呼叫 resolve 函式,並把結果透過參數傳遞進去
}, delayTime)
})
}
async function test() {
// 比較久,先等 result1 再等 result2
let result1 = await delayedAdd(3, 4, 2000)
let result2 = await delayedAdd(2, 3, 3000)
let answer = result1 * result2
console.log(answer)
}
初期:不要想太多,語法流程是怎麼就是怎樣
資料參考:
回呼函式 Callbacks、Promises 物件、Async/Await 非同步流程控制 - 彭彭直播 at 2019/04/07