# 什麼是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的物件),所以才可以直接使用。

### 自己建立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)的題目:

簡單來說就是當別人輸入一個數字,函式就要等待那個時間並且回傳一個promise的物件,用前面的學的東西好好思考一下吧!
:::success
:::spoiler 看解答 :eyes:
```javascript=
async function sleep(millis) {
return new Promise((resolve, reject) => {
setTimeout(()=>{
return resolve(millis);
},millis)
});
}
```
:::