為什麼需要Promise?
我們需要Promise的原因是需要解決以下幾種問題:
1. 回呼地獄
當我們需要安排很多個非同步函式的操作順序時,必須在一個函式執行結束之後,
再呼叫其他的非同步函式繼續執行下去,這樣會導致程式碼很亂,而且不容易管理。
2. 寫法不一致
在許多的非同步套件的寫法都不太一樣,這樣的話就會導致程式碼的整體寫法不夠一致。
3. 無法同時執行
在JS的程式碼中,我們無法保證每一個非同步函式都是同時執行。
另外,有時候,我們也希望當所有指定的非同步函式都執行完畢之後,在執行某些內容。
如果,沒有適當的安排的話,這樣的效果是非常難完成的。
那以上就是沒有Promise出現之前,常常遇見的問題囉。
以下先舉個解決回呼地獄的範例
假設我們有在專案檔裡面安裝了axios這個Promise套件
1 | const api = 'https://randomuser.me/api/' |
可以看到以上這個範例在axios套件裡面的Promise就有固定的寫法,利用get
來執行ajax的行為,利用then
來安排非同步函式的執行順序,
這樣的寫法就解決了寫法不一致 和 回呼地獄的問題。
另外,再舉個解決無法同時執行非同步的範例
1 | const api = 'https://randomuser.me/api/' |
我們利用了Promise.all
的方式,來確保同時執行兩個非同步的函式,並在這兩個函式執行完畢後,才接著執行then
後面接的內容。
Promise 基礎概念
當我們創建了一個Promise物件,並執行它,此時,會處於pending狀態,當執行結束之後,會有兩種結果,
一個是執行成功,這個時候會進到then
裡面的結果,並回傳Promise物件中的resolve
內容。
另一個是執行失敗,這個時候會進到catch
裡面,並回傳Promise物件中的reject
的內容。
首先,先上個簡單的範例
1 | const a = new Promise((resolve, reject) => { |
你可以看到,我們利用a來建立一個Promise物件,那這邊要特別注意的是,建立Promise物件時,必須要傳入一個callback function,才能順利建立喔,然後,這個callback function傳入的參數分別是resolve
和reject
。
第二,你可以看到我們在建構Promise物件中有一個resolve
內容,那這個內容就會在我們調用a物件時,並呼叫then
,就會回傳resolve
的結果。
若今天,你在建構Promise物件內容中,也有加入reject
的內容的話,就會在調用Promise物件並呼叫它catch
時,就會回傳reject
的結果。
但是,我們一般都不會這樣創建與使用Promise物件的,因為,上面這個範例a是一個物件,無法傳任何參數進去,使用上比較沒有彈性。
所以,通常我們都會用function回傳一個Promise物件,這樣使用上會比較有彈性。
這邊,就使用函式來創建一個Promise物件並使用它
1 | function PromiseFn(num) { |
上面這個範例,就是利用function回傳一個新的Promise物件,並對這個函式傳入一個參數。
那在這邊我們另外想要特別的呈現js的同步和非同步之間的執行次序的差別,setTimeout
是一個非同步函式,那console.log
是一個同步函式,
所以,以上的範例的執行結果,會先執行’程式結束’之後,最後,才會執行在Event Queue裡面的setTimeout
這個非同步的內容,所以,接著才會呈現Promise最終執行的結果。
鏈接技巧
當我們希望安排非同步函式們的執行順序時,我們就需要用到Promise chain的技巧來安排非同步函式之間的執行順序。
先來個例子
1 | function PromiseFn(num) { |
上面這個範例說明了,當我們想要安排非同步函式執行順序,必須在a函式執行完畢的內容中,加上一個return
後面接的就是接著要執行的非同步函式。
如此,就可以將接續執行的非同步函式的內容再傳遞到下一個Promise中。
如果,中間只要執行有失敗就會直接跳到執行catch
的內容,若catch
的內容裡面也有return
接著要執行的非同步函式,它就會接著再執行後面的內容。
then可以執行resolve和reject
之前雖然有說then是執行resolve的結果,catch是執行reject的內容。但其實then都可以執行resolve和reject的結果喔。
1 | PromiseFn(10) |
上面的範例你可以看到在then裡面,就同時有放resolve和reject要執行的結果。
Promise 常用方法
這邊介紹了常用的兩個Promise的api分別是Promise.all
和 Promise.race
Promise.all
1 | function PromiseFn(num, time = 500) { |
上面這個範例可以看到,我們傳入三個非同步函式到Promise.all
裡面,當傳入的三個非同步函式皆執行成功,就會進到then
的內容,那這個res會回傳一個陣列,這個陣列存的就是Promise.all
裡面的函式回傳的resolve
的結果。
若Promise.all
裡面只要一個函式執行失敗,就會直接進到catch
的執行程序中。
Promise.race
這個Promise的api的功能,傳入的函式們,只要有任一個函式第一個執行完,就會直接回傳它的執行結果,所以,如果這個第一個執行完的函式是執行成功,
就會進到then
的執行程序,如果,如果這個第一個執行完的函式是執行成功失敗,就會進到catch
的執行程序。
1 | function PromiseFn(num, time = 500) { |
上面這個範例,第一個執行完的會是PromiseFn(0, 50),而因為它是執行失敗會回傳reject
的內容,所以,接著會執行catch
的內容。
Promise 與 Ajax
通常Promise在專案裡面我們都會搭配Ajax跟遠端請求資料的程式內容一起搭配使用。
1 | const url = 'https://jsonplaceholder.typicode.com/todos/1' |
上面這個範例,我們創了一個回傳新Promise物件的函式,並且在它的Promise內容中,是跟遠端索取資料。
最後,我們再搭配Promise來執行這個非同步功能的索取遠端資料的執行程序。