# 什麼是Promise物件? ## 前言 撰寫JS或是維護的程式碼的時候,不外乎會看到``.then(), new Promise, resolve(), reject()``其實都和今天要介紹的promise有關係,又或是很多人常用promise方法卻不太理解其背後的觀念,今天這篇文章就來向大家仔細介紹,到底什麼是Promise! ## 同步(synchronous)和異步(asynchronous) ### 簡介 在程式中,異步(asynchronous)代表著一個process獨立於其他 process運行,而同步(synchronous)代表著一個process僅在某個其他process完成或移交後而運行。可以把同步想像成只有一個人在做事,如果有五個不同的函式,同步的過程中,會按照順序執行,第一件事情做完,才能做第二件事情;反觀異步的過程中,可以想像有很多人同時進行,同樣的五個函式,異步的狀況,可以請五個人同時做這五件不同的事情,時間效率大大提升! ### JavaScript 是同步還是異步? 通常來說,JavaScript的特性是single-threaded synchronous,也就是說JS單打獨鬥是一次只會做一件事情的程式語言。當然,JS也有自己內建的異步函式(asynchronous function),例如常見的計時器setTimeout(),Promise以及async/await也是常見的異步函式。使用此函式的時候會另外開一個process且不影響原本process的執行 ## Promise 在幹嘛 剛剛提到了Promise 是 JavaScript 中異步的基礎。Promise 是一個由asynchronous function所return的==物件==,主要功能是,Promise 會代理一個建立時不用預先得知結果的值。 ### 三種狀態 一個 Promise 物件有處於以下三種狀態: - 擱置 (pending):起始狀態,剛被建立的時候會是pending。 - 實現 (fulfilled):表示操作成功地完成。 - 拒絕 (rejected):表示操作失敗了。 例如我們在使用Ajax的fetch或axios方法要去其他網站取得資料的時候會用,此時axios會回傳一個promise的物件,且狀態為pending,等待真正取得資料後,才會變成fulfilled,如果過程中有任何錯誤(可能要求的伺服器出問題時)就會變成rejected。 當Promise成功進入fulfilled狀態時,才會執行.then()中的內容,反之當進入rejected狀態時,就會去執行.catch()的內容 ```javascript= const promiseObject = axios.get('https://example.com/api/') console.log(promiseObject) // Promise { <pending> } promiseObject.then(data =>{ console.log(data) // fulfilled ->print data }).catch(err => console.log(err)) // rejected -> print err ``` ### 建構函式 剛剛提到promise本身是有三個狀態的物件之外,他本身其實也是一個建構函式(函式也是屬於物件的一種),因此可以直接使用相關的方法,透過 console 的結果可以看到 Promise 可以直接使用 all、race、resolve、reject,此外前面提到的then, catch原型方法(在 prototype 內),因為已經是是Promise 建構函式建立的物件(可以使用new建立,或是前面提到的fetch本身就會回傳給你一個Promise的物件),所以才可以直接使用。 ![截圖 2024-01-13 下午3.31.38](https://hackmd.io/_uploads/SJ-_2nJKp.png) ### 自己建立Promise物件 除了某些函式本來就會回傳promise物件之物,我們當然也可以自己建立,而其中resolve, reject分別代表當狀態進入fulfilled, rejected要執行的callback function 以下方產生隨機結果的例子,如果num的結果是1就會進入fulfilled狀態去執行resolve函式,然後在下方執行的時候要用.then和.catch來去得promise物件的內容 ```javascript= function testPromise() { return new Promise((resolve, reject) => { // 隨機取得 0 or 1 const num = Math.random() > 0.5 ? 1 : 0; // 1 則執行 resolve,否則執行 reject if (num) { resolve('成功'); } reject('失敗') }); } testPromise() .then((success) => { console.log(success); }, (fail) => { console.log(fail); }) ``` - Promise.all() 有時,我們需要確保所有獨立的Promise都進入fulfilled狀態再去做其他事,這時就可以用Promise.all()這個方法,給一個promise array並返回一個 promise。 稍微修改一下上面的範例,設定傳入一個數字且當數字大於1,就會進入fulfilled狀態,否則就是rejected,再利用Promise.all確保所有的結果都是fulfilled ```javascript= function testPromise(num) { return new Promise((resolve, reject) => { return num >=1 ? resolve('成功'):reject('失敗') }); } Promise.all([promise(1), promise(2), promise(3)]) .then(res => { console.log(res); //[ '成功', '成功', '成功' ] }).catch(err =>{ console.log(err) }); //只要有一個失敗就會進入rejected狀態,去執行catch的內容 Promise.all([promise(1), promise(0), promise(3)]) .then(res => { console.log(res); }).catch(err =>{ console.log(err) // '失敗' }); ``` ## Async/ await Js中還有一個更簡單的方法來處理promise函式! 在asynchronous function中,可以在 promise 的函數前加上 await 。如此一來,程式會在該點等待直到Promise被fulfilled或是rejected。 > p.s. await 關鍵字只能放在async function內部! ```javascript= async function myAsyncFunction() { const promiseObject = await axios.get('https://example.com/api/') console.log(promiseObject) // promiseObject's result } myAsyncFunction() ``` ### try...catch 前面提到可以用then()或catch()取得fulfilled, rejected狀態下的結果,但是如果我們用上面await的方式,只能取得fulfilled的結果,這時如果執行的過程中promise狀態變成rejected程式就會大爆炸,可能會直接中斷,所以我們需要另外一種錯誤處理的方式,那就是try...catch... 將要執行的程式碼放到try裡面,只要有任何錯誤就會直接跳到catch! ```javascript= try{ myAsyncFunction() }catch(err){ console.log(err) } ``` :::warning :warning: async function都一定會return 一個 Promise Object,不論我們在async function內return什麼值!! ```javascript= async function myFunction() { return 10; } let promise = myFunction(); console.log(promise) // Promise { 10 } promise.then(data => {console.log(data);}) // 10 ``` ::: ## 後記 leetcode 如果大家有機會練習leetcode上的js題目,了解異步函式是一件非常基本的事情,可以參考 2621. Sleep (Easy)的題目: ![截圖 2024-01-13 下午5.30.00](https://hackmd.io/_uploads/S1IQuRJF6.png) 簡單來說就是當別人輸入一個數字,函式就要等待那個時間並且回傳一個promise的物件,用前面的學的東西好好思考一下吧! :::success :::spoiler 看解答 :eyes: ```javascript= async function sleep(millis) { return new Promise((resolve, reject) => { setTimeout(()=>{ return resolve(millis); },millis) }); } ``` :::