Day.28 「Promise 初體驗~」 —— ES6 Promise
我們前面已經學習了回調函式(Callback Function)與構造函式(Constrcutor),而 Promise 是 ES6 新增用來解決非同步回調地域的新語法,同時也是一個構造函式!
非同步
在這裡我們要先了解到什麼是非同步!相信大家應該都聽過最好理解的範例,那就是用餐廳來做範例!
同步的概念就像是:服務生接收點餐 → 通知廚房有餐 → 廚房完成餐點 → 結帳 → 接下一位客人
一步一步做下去,優點是不易出錯,但缺點也非常明顯,效率非常差。
而非同步的概念:服務生接收點餐 → 通知廚房有餐 → 結帳 → 接下一位客人 → … → 廚房完成餐點一起給客人
能夠把需要先執行的優先執行,優點就是效率好,但缺點就是跟同步比起來,維護比較麻煩。
而在我們介紹定時器時,就有體現出非同步的狀態。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| function order() { console.log("點餐"); (function making() { console.log("開始製作");
(function checkout() { console.log("結帳"); })(); setTimeout(()=>{ console.log("餐點完成"); }, 1000) })(); }
order(); order();
|
這時就有點看到回調地獄(Callback Hell)的影子了!這就是它不容易維護的部分
而 Promise 改善了回調地獄的問題。
ES6 以前
在還沒有 ES6 前,處理 AJAX 與 計時器的時候,都是直接使用回調函式來處理非同步事件
這裡用抽獎為例
1 2
| <button id="btn">點我抽獎</button>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const btn = document.getElementById("btn");
function randomNum (m, n) { return Math.ceil( Math.random() * (n - m + 1)) + m - 1; }
btn.addEventListener("click", function(){ setTimeout( function () { let n = randomNum(1, 100); if ( n <= 30 ) { console.log("恭喜你中獎了!你的中獎數字是" + n); } else { console.error("銘謝惠顧~你的數字是" + n) } }, 1000) })
|
Promise
在 ES6 之後,可以透過 Promise 來包裝程式碼,
而使用 Promise 的方式,與構造函式的使用方式類同,而參數帶入的是函式,帶入的函式內會有兩個參數 resolve
、 reject
。
1 2 3
| const p = new Promise( (resolve, reject) => { })
|
這兩個參數本身也是函式,一個代表解決,一個代表拒絕,函式的參數可以進行傳遞。
1 2 3 4 5 6 7
| const p = new Promise( (resolve, reject) => { if ("成功") { resolve( "成功" ); } else { reject( "失敗" ); } })
|
以上面的抽獎例子做修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
btn.addEventListener("click", function(){
const p = new Promise((resolve, reject) => { setTimeout(() => { let n = randomNum(1, 100); if ( n <= 30 ) { resolve(n); } else { reject(n); } }, 1000) }); })
|
這樣就包裝好了,但你會發現,奇怪怎麼沒有效果了?
那是因為還要調用 then
方法,來接收成功或失敗的資料,一樣可以接收兩個參數,兩個參數分別代表成功與失敗的函式,而成功與失敗的函式可以靠參數傳遞資料。
then
1 2 3 4 5
| p.then((data)=>{ console.log("恭喜你中獎了!你的中獎數字是" + data); },(err)=>{ console.error("銘謝惠顧~你的數字是" + err) })
|
你可能覺得,好像沒有方便到哪裡呀~還要另外用 then
來調用!
那是因為我們這個範例還很簡單,沒有到 Callback Hell 的程度,當資料越來越複雜,就會形成 Callback Hell。
1 2 3 4 5 6 7 8 9 10 11
| data.readFile('./data/a.text', (err, data1) => { data.readFile('./data/b.text', (err, data2) => { data.readFile('./data/c.text', (err, data3) => { data.readFile('./data/d.text', (err, data4) => { let result = data1 + data2 + data3 + data4; console.log(result) }) }) }) })
|
catch
而 Promise
只要包裝好了,接下來只要使用 then
來進行連續調用串接,不會讓程式碼越來越往右推移。
此外大多數情況,也不會刻意接失敗的資料,可以依靠 catch
來進行最後失敗時的處理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const p = new Promise((res,rej) => { data.readFile('./data/a.text', (err, data) => { res(data); }) })
p.then( val => { return new Promise((res, rej) => { data.readFile('./data/b.text', (err, data) => { res([val, data]); }) }) }).then( val => { return new Promise((res, rej) => { data.readFile('./data/c.text', (err, data) => { val.push(data); res(val) }) }) }).then( val => { console.log(val) }).catch( err => { console.error("串接失敗!") })
|
總結
雖然短期這樣看,Promise 寫起來好像沒有 Callback 快,但它解決了長期的資料變龐大的時候,所產生的回調地獄,Promise 只是向下添加程式碼,而 Callback Hell 則是一直往右推移程式碼,Promise 還有很多方法還沒講到,目前只是初體驗!
參考資料