---
# System prepended metadata

title: 主角還沒上場，配角怎麼偷跑了? 用 Promise 來改善吧!
tags: [' 六角筆記王', JavaScript]

---

---
tags:  JavaScript, 六角筆記王
title: 主角還沒上場，配角怎麼偷跑了? 用 Promise 來改善吧!
---

# 主角還沒上場，配角怎麼偷跑了? 用 Promise 來改善吧! 
Promise 是 ES6 所新增的建構函式，常用來處理同步與非同步的問題，也增強了程式碼的可讀性。而 JS 的執行順序是由上到下，若遇到事件則為進入事件佇列( Event queue )中，就是非同步的問題，像是 : 
- AJAX 行為
- 監聽事件(click、change、…)
- `setTimeout( () => { } )`


## Promise 與 AJAX
我們可以用原生 fetch 語法或是直接使用第三方套件的 Axios 來做 AJAX 行為。

這邊提供一個資料測試網址([點我](https://randomuser.me/api))，它可以取得一些 JSON 格式的假資料。

### axios
以下是 axios 套件寫法 : 
```javascript 
// 利用 axios 的 get 方法取得資料
// 可以接收資料後， return 資料出來傳入下一個 then 使用
var url = 'https://randomuser.me/api'

axios.get(url).then(res=>{
  console.log(res)
}).catch(err =>{
  console.log(err)
})
```

### fetch
利用 fetch 原生語法來進行 :
```javascript 
var url = 'https://randomuser.me/api'

fetch(url)
  .then(response => {
    // 必須從 response中 return response.json()給下一個 then
    // response 為被鎖定的名為的物件，ReadableStream，其中一個方法會回傳 JSON格式
    // 但是網址請求已被使用中，必須使用下一個 then 再度請求後回傳
    return response.json(); 
  })
  .then(jsonData => {
    console.log(jsonData);
  })
```

### XMLHttpRequest
```javascript 
var url = 'https://randomuser.me/api'

function get(url){
  return new Promise((resolve, reject)=>{
    var req = new XMLHttpRequest()
    req.open('GET', url)
    req.onload = function (){
      if(req.status = 200){
        // 成功時
        resolve(req.response)
        console.log(req.response)
      } else {
        // 失敗時
      }
    }
    req.send()
  })
}
get(url).then((res)=>{
  console.log('get', res)
}).catch((err)=>{
  console.log(err)
})
```


## Promise 與事件佇列
通常我們會使用一些監聽事件等待使用者去觸發，或是用一個計時器，例如這樣 : 

### 一般事件佇列
```javascript 
// setTimeout秒數就算設定為0，也會是最後出現
function fn(){
  setTimeout( () => {
    console.log('等待出現')
  }, 0)
  alert('使用者點擊')
}

var btn = document.querySelector('.btn')
btn.addEventListener('click', fn)
```

但如果目標是讓每經過一秒顯示一次呢?
```javascript 
// 這樣會變成 1 秒後一起出現
// 而非每秒出現一次
// 當然也可以設計成 1000 > 2000 > 3000，但這不是我們要的
setTimeout( () => {
  console.log('等待出現')
}, 1000)
setTimeout( () => {
  console.log('等待出現')
}, 1000)
```

### 透過 Promise 來操作事件佇列
像是上述的例子，可以透過 Promise 來處理問題。

以下透過 Promise 來操作事件佇列 : 
```javascript 

function activeFn(time){
  return new Promise((resolve, reject)=>{
    setTimeout( () => {
      resolve(`${time}毫秒後自動顯示`)
    }, time)
  })
}

activeFn(1000).then(res=>{
    // 顯示第一次
    console.log(res)
    return activeFn(1000)
  }).then(res=>{
    // 顯示第二次
    console.log(res)
  })

```

## Promise 狀態

```graphviz 
digraph graphname {
  
  P [label="Promise" color=Block, fontcolor=Block, fontsize=24, ]      
  
  T [label=".then()" color=Blue, fontcolor=ForestGreen, fontsize=24, shape=box]  
 
  C [label=".catch()" color=Blue, fontcolor=Red, fontsize=24, shape=box]
  
  P->T [label="  fulfilled     ", fontcolor=block, ] 

  P->C [label="   rejected", fontcolor=block]
}
```


Promise會有三種狀態，分別是 : 

| 狀態名稱 | 意義 | 
| -------- | -------- | 
| Pending    | 待機 / 未確認，以 AJAX 行為來說，就是傳入網址等待取回資料   | 
| Fulfilled    | 已實現狀態，以 AJAX 行為來說，就是取值成功時   | 
| Rejected    | 已否決狀態，以 AJAX 行為來說，就是取值失敗時  | 

以上一次只會有一種狀態，並在實際使用時分別用不同的關鍵字取用。
- 若進入 fulfilled
    - 使用 then 接收上一個 return 的值
    - 使用函式將值作為參數傳入 `.then((參數名稱)=>{console.log(參數)})`
- 若進入 rejected
    - 使用 catch 接收錯誤時會回報的值
    - 使用函式將值作為參數傳入 `.catch((參數名稱)=>{console.log(參數)})`

## Promise 建立
- Promise 是一個內建的函式 `(typeof Promise) // "function"`
- 可以使用 new 運算子轉為物件，但它需要傳入一個 function 才能運作
- 根據使用者認為該
    - 情況正確使用 `resoleve()` 回傳資料
    - 若不正確是用 `reject()` 回傳失敗訊息
- resolve 與 reject 可自定義參數名稱，不過為了避免混淆，還是習慣使用預設名稱

```javascript=
function promiseFn(num){
  return new Promise((resolve, reject)=>{
    // 非同步行為
    setTimeout(()=>{
      if(num){
        resolve('成功!')
      } else {
        reject('失敗!')
      }
    }, 10)
  })
}
promiseFn(1).then((res)=>{
  console.log(res)
}).catch((res)=>{
  console.log(res)
})
```


## Promise 回傳資料與串接
```javascript=
function promiseFn(num){
  return new Promise((resolve, reject)=>{
    // 非同步行為
    setTimeout(()=>{
      if(num){
        resolve('成功!')
      } else {
        reject('失敗!')
      }
    }, 10)
  })
}
promiseFn(1).then((res)=>{
  console.log(res)
  return promiseFn(2) 
  // 這個回傳的值會給下一個 then
  // 若值為假值 promiseFn(0)，會直接跳至 catch
}).then((res)=>{
  console.log(res)
}).catch((res)=>{
  console.log(res)
  return promiseFn(4)
  // 這個回傳的值會給下一個 then
}).then((res)=>{
  console.log(res)
})

// 直接使用 then傳入兩個函式 then(函式 1, 函式 2)，
// 前者接收 resolve狀態，後者 reject狀態
promiseFn(1).then(
  (res)=>{
    console.log(res, 'success')
    return promiseFn(0) // 會傳給下一個 then 接收
  }, 
  (rej)=>{
    console.log(rej, 'fail')
    return promiseFn(1) // 會傳給下一個 then 接收
  }).then(
  (res)=>{
    console.log('成功!')
  }, 
  (rej)=>{
    console.log('失敗!')
  })
```

## Promise 常用資料回傳方法
- Promise.all 
    - 等待全部執行完畢，並回傳所有結果
    - 途中只要有狀態為 reject，會直接回傳 catch 結果
- Promise.race
    - 只會回傳第一個執行完畢的函式並根據狀態回傳結果
    - 只會回傳一組資料

### Promise.all
```javascript 
function promiseFn(num, time){
  return new Promise((resolve, reject)=>{
    // 非同步行為
    setTimeout(()=>{
      if(num){
        resolve('成功!')
      } else {
        reject('失敗!')
      }
    }, time)
  })
}
promiseFn(1, 2000).then((res)=>{
  console.log(res)
})

// 只要途中有一個執行失敗的話，就會進入 reject狀態並回傳結果，不會再往下執行
Promise.all([
  promiseFn(1, 1000),
  promiseFn(0, 2000),
  promiseFn(1, 3000)
]).then(res=>{
  console.log(res[0], res[1], res[2])
})
```

### Promise.race
```javascript 
function promiseFn(num, time){
  return new Promise((resolve, reject)=>{
    // 非同步行為
    setTimeout(()=>{
      if(num){
        resolve('成功!')
      } else {
        reject('失敗!')
      }
    }, time)
  })
}
promiseFn(1, 2000).then((res)=>{
  console.log(res)
}).catch((res)=>{
  console.log(res)
})

// 只取第一個先跑完的結果，再根據狀態回傳
Promise.race([
  promiseFn(1, 1000),
  promiseFn(1, 500),
  promiseFn(1, 3000)
]).then(res=>{
  console.log(res)
}).catch((res)=>{
  console.log(res)
})
```


## 事件佇列與一般結果
若想在事件佇列執行完畢後顯示其他結果，該怎麼做?
```javascript 
function activeFn(time){
  setTimeout( () => {
    console.log(`${time}毫秒後自動顯示`)
  }, time)
}
function activeFn2(){
  console.log('最後顯示')
}
activeFn(3000)
activeFn2()
// "最後顯示" 
// '1000毫秒後自動顯示"
```

### 利用 Promise 修正結果 
```javascript 
function activeFn(time){
  return new Promise((resolve, reject)=>{
    setTimeout( () => {
      resolve(`${time}毫秒後自動顯示`)
    }, time)
  })
}
function activeFn2(){
  return new Promise((resolve, reject)=>{
    resolve(`最後顯示`)
  })
}

// 執行函式 > 接收結果，return 函式執行的結果 > 接收結果 

activeFn(3000).then(res=>{
  // 顯示上一個函式執行的結果
  console.log(res)
  // return 另一個函式執行的結果
  return activeFn2()
  
  }).then(res=>{
  // 顯示上一個函式執行的結果
  console.log(res)
})
// "3000毫秒後自動顯示"
// "最後顯示" 
```

## 參考來源
> 1. 六角學院 - 核心篇
> 2. [ OXXO.STUDIO - JavaScript 同步延遲 ( Promise + setTimeout )](https://www.oxxostudio.tw/articles/201706/javascript-promise-settimeout.html)

<!-- {%hackmd S1DMFioCO %} -->