---
title: JavaScript核心篇 - 同步語言、非同步行為、Promise
tags: 同步語言, 非同步行為, Promise
description:
---
JavaScript核心篇 - 同步語言、非同步行為、Promise
===
### 同步語言的概念
:::success
JavaScript是**同步語言**,其程式碼是**逐行執行**,由上而下的執行,執行完這一行再執行下一行。
:::
```javascript=
console.log('a'); // a
console.log('b'); // b
console.log('c'); // c
```
<br>
### 非同步的例子
:::success
JavaScript是**同步語言**,但又**有執行非同步**的能力。
:::
#### ex1:`console.log(1)~(4)`執行的順序為何?
- 一開始會執行**立即函式**的部分。所以先執行`console.log(1)`。
- `setTimeout(() => {}, 0)`是**非同步行為**。不管`setTimeout`延遲多久的時間,==只要是非同步行為就是**最後**才執行==。
- 接著執行**立即函式以外**的`console.log(4)`。
- 最後執行`setTimeout(() => {}, 0)`內的`console.log(2)`、立即函式的`console.log(3)`。
#### 執行的順序:console.log(1)、(4)、(3)、(2)。
```javascript=
(() => {
console.log(1);
setTimeout(() => {
console.log(2);
(() => {
console.log(3);
})();
}, 0);
})();
console.log(4);
```
#### ex2:執行的順序為何?
- `setTimeout`非同步行為,所以**最後**執行。
- 先執行函式`getName()`,所以`console.log()`會先印出`小明`。
- 最後`setTimeout`的部分,`myName`賦予字串`小王`,再執行函式`getName()`,所以`console.log()`會先印出`小王`。
```javascript=
function getName() {
console.log(myName);
};
let myName = '小明';
setTimeout(() => {
myName = '小王';
getName();
}, 0);
getName();
```
#### ex3:`setTimeout`所需花費的時間為100ms?
- JavaScript是**逐行執行**的。會先等`for`迴圈**執行完成**後,再回頭執行`setTimeout`的部分。
- 所以在迴圈**執行完之前**,就算`setTimeout`**延遲時間到**,==也不會立即執行作用域內的程式碼==。

```javascript=
console.time('setTimeout時間');
setTimeout(() => {
console.timeEnd('setTimeout時間');
}, 100);
let id = 0;
for (let i = 0; i < 100000000; i++) {
id = parseInt(Math.random() * i);
};
```
<br>
### 認識逐行執行的 javaScript
#### 從範例了解==逐行執行==的JavaScript
```javascript=
console.time('執行時間');
function planA (){
console.log('planA');
};
function planB() {
console.log('planB');
};
function planC() {
console.log('planC');
};
(function planOrder () {
planA();
planB();
planC();
})();
console.timeEnd('執行時間');
```
#### 狀況一:把`planB`作用域加上`a`
- 因為`a is not defined`,所以立即函式`planOrder`會中斷。
```javascript=
function planB() {
a;
console.log('planB');
};
```
由此可知
:::success
JavaScript是逐行執行,執行過程中出錯,程式就會中斷。
:::
<br>
#### 狀況二:把`planB`作用域加上`for`迴圈
- 因為立即函式`planOrder`作用域**逐行執行**,所以會等`planB`作用域的`for`迴圈結束後,再去執行`planC`。
- 所以`console.timeEnd('執行時間')`也會跟著變長。

```javascript=
function planB() {
console.log('planB');
let id = 0;
for (let i = 0; i < 100000000; i++) {
id = parseInt(Math.random() * i);
};
};
```
<br>
#### 狀況三:把`planB`作用域的`console.log('planB')`外層加上`setTimeout`延遲500ms。請問立即函式`planOrder`內的執行順序會改變?
:::success
非同步行為,因為**非立即執行**。所以**非同步行為**先放置再==事件佇列==,等最後再執行。
:::
- 所以運行順序是:
- 就算把`setTimeout`延遲**改0ms**,運行順序還是**不變**。
```javascript=
function planB() {
setTimeout(() => {
console.log('planB');
}, 500);
};
```
<br>
### 執行堆疊(Call Stack)
:point_right: [執行堆疊](https://medium.com/魔鬼藏在程式細節裡/淺談-javascript-執行環境-2976b3eaf248)
- 在範例中第1、3、8、10、16、22、24、26、28行,加入`debugger`中斷點,
觀察執行堆疊的變化。
- `function`沒有被呼叫,其**作用域的函式不會執行**。
- 範例主要是觀察**立即函式**`planOrder`**執行堆疊的變化**。
```javascript=
debugger
console.time('執行時間');
function planA (){
debugger
console.log('planA');
};
function planB() {
debugger
setTimeout(() => {
debugger
console.log('planB');
}, 500);
};
function planC() {
debugger
console.log('planC');
};
(function planOrder () {
debugger
planA();
debugger
planB();
debugger
planC();
debugger
})();
console.timeEnd('執行時間');
```
#### 執行堆疊的流程
1. 一開始在全域執行環境。

3. 進入到`planOrder`

2. 一開始進入`planA`

3. 回到`planOrder`

3. 再進入到`planB`

此時`setTimeout`加入==事件佇列==

4. 回到`planOrder`

5. 進入到`planC`

6. 回到`planOrder`

7. **最後**執行`setTimeout`的部分(**async 非同步**)


<br>
### setTimeOut 的作用
- JavaaScript**本身沒有**`setTimeout`的功能,JavaaScript把`setTimeout`交給瀏覽器。
- 呼叫`setTimeout`這個函式,JavaScript把**延遲計時**這件事交給瀏覽器處理。
- 瀏覽器會要求`setTimeout`**準備函式來接收**,這個函式就是`setTimeout`==裡面的callback function==。
- `setTimeout`裡面會加上延遲時間,這個過程交給瀏覽器處理,JavaaScript是**逐行執行**,執行完就結束了。
- 瀏覽器處理完`setTimeout`後(**延遲計時**),把結果往回丟給`setTimeout`內的==callback function==去做處理。
:::success
JavaaScript執行`setTimeout`(非同步行為)時,先把`setTimeout`callback function放到**事件佇列**。
==(執行堆疊會進到`setTimeout`但不會執行內部callback function)==,
等到JavaaScript逐行執行的**同步行為完成後**,**瀏覽器再呼叫**事件佇列的`setTimeout` **callback function**
:::

<br>
### 為何要使用 promise
- `setTimeout`裡面的callback function,並延遲100ms,印出`這是非同步的函式`。
```javascript=
function asyncFn () {
setTimeout(() => {
console.log('這是非同步的函式');
}, 100);
};
asyncFn();
```
- 把`setTimeout`裡面的callback function**獨立出來**,**賦予到變數**上,符合==高階函式的寫法==。
```javascript=
function asyncFn() {
const fn = () => {
console.log('這是一段非同步的函式');
};
setTimeout(fn, 100);
};
asyncFn();
```
- 把`setTimeout`裡面的callback function作為**參數代入**。
```javascript=
function asyncFn(fn) {
setTimeout(fn, 100);
};
asyncFn(() => {
console.log('這是一段非同步的函式');
});
```
- 現在也把`setTimeout()`裡面的延遲時間,拉出來作為參數。
```javascript=
function asyncFn(fn, time) {
setTimeout(fn, time);
};
asyncFn(() => {
console.log('這是一段非同步的函式1');
}, 100);
asyncFn(() => {
console.log('這是一段非同步的函式2');
}, 300);
asyncFn(() => {
console.log('這是一段非同步的函式3');
}, 100);
```
可以看到執行印出的順序。

如果要把印出的順序,改成`這是一段非同步的函式1、2、3`**逐行執行**的方式。則要把呼叫`asyncfn()`的方式改成**巢狀**。
```javascript=
function asyncFn(fn, time) {
setTimeout(fn, time);
};
asyncFn(() => {
console.log('這是一段非同步的函式1');
asyncFn(() => {
console.log('這是一段非同步的函式2');
asyncFn(() => {
console.log('這是一段非同步的函式3');
}, 300);
}, 600);
}, 900);
```
<br>
### promise 是什麼?
:::success
Promise是函式、函式建構子、物件。
:::
```javascript=
console.dir(Promise);
```

<br>
#### 建立自己的Promise函式

- returnu 一個`new Promise`的實體(也可稱為函式、函式建構子、物件) ,裡面放入`callback Function`再代入兩個參數`resolve`、`reject`。
- Promise成功的話,`resolve`會用`then()`插入`callback Function`==接收資料==。
- Promise失敗的話,`reject`會用`catch()`插入`callback Function`==接收資料==。
- 在`new Promise`的實體加入`setTimeout`根據`promiseFn()`代入的參數`true`、`false`決定`then`或`catch`哪個能接收到資料。
```javascript=
function promiseFn (boolean) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (boolean) {
resolve('成功');
} else {
reject('失敗');
};
}, 500);
});
};
promiseFn(false)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
```
<br>
### 鏈接
:::success
以前非同步行為都用`callback Function`來接收。
如果**要求執行順序**的話,就用**巢狀**的撰寫方式來接收。
現在如果是用Promise,就可用**鏈接**的方式撰寫。
:::
- `promiseFn()`第一次執行之後,如果要鏈接下一個`promiseFn()`,在第一次**成功**執行的`.then()`的callback Fn作用域內,加上`return promiseFn()` 。
- 後續還需要鏈接下一個`promiseFn()`,以上一個例子類推。
```javascript=
function promisefn (boolean) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (boolean) {
resolve('成功');
} else {
reject('失敗');
}
}, 500);
});
};
promiseFn(true)
.then((res) => {
console.log('第一次', res); // 第一次執行
return promiseFn(true);
})
.then((res) => {
console.log('第二次', res); // 第二次執行
return promiseFn(true);
})
.then((res) => {
console.log('第三次', res); // 第三次執行
});
```
#### 鏈接失敗
- 鏈接失敗會在**最後面**加上`.catch()`,用來==處理前面所有`.than()`的錯誤==。
- 在第4行**第二次**鏈接是**失敗的**,會直接跳到`.catch()`,裡面callback Fn的`console.log`會印出**失敗**。
- 第15行會**再次**`return promisefn(true)`,如果還是**鏈接失敗**瀏覽器console會顯示**Uncaught(in promise)失敗**

**通常不會再從`.catch()`裡面再接`.then()`**。
```javascript=
promisefn(true)
.then((res) => {
console.log('第一次', res); // 第一次執行
return promisefn(false);
})
.then((res) => {
console.log('第二次', res); // 第二次執行
return promisefn(true);
})
.then((res) => {
console.log('第三次', res); // 第三次執行
})
.catch((err) => {
console.log(err);
return promisefn(true);
});
```
<br>
### axios的語法與取值
**axios**是**Promise**為基底。
```javascript=
console.dir(axios);
```

- axios本身是**函式**也是**物件**,用`get()`這個方法**取得遠端資料**。
(以random user API測試)
- 再用`.then()`的方式**接收成功回傳的資料**。
- 以及用`.catch()`的方式**接收失敗回傳的資料**。
```javascript=
axios.get('https://randomuser.me/ap/')
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
})
```
<br>
### 如何撰寫==連續性==請求(Request)
#### 連續性也可看做==依照順序==請求
**範例:**
在**random user** API用**seed**可以取得**相同一筆資料**,用==Promise鏈接==的手法,使用axios套件**連續取得兩筆**資料,第一筆是**隨機**取得,第二筆是用第一筆資料**seed**取得,最後印出兩筆資料的Email都是==相同的==。
- 先取得`.then()`接收的第一筆資料裡的email`res.data.results[0].email`。

- 再用**物件解構**`const {seed} = res.data.info`得到第一筆資料的**seed**值。

- 以**Promise鏈接**取得第二筆資料(要代入第一筆資料seed值)。
`.get()`內,使用樣板字面值`https://randomuser.me/api/?seed=${seed值}`
:point_right: [Random User文件](https://randomuser.me/documentation#seeds)

- 再取得`.then()`接收的第二筆資料裡的email`res.data.results[0].email`。
- 鏈接失敗會在最後面加上`.catch()`,用來處理前面所有`.than()`的錯誤,callback Fn內`console.log()`再印出錯誤資料。
```javascript=
axios.get('https://randomuser.me/api/')
.then((res) => {
console.log(res.data.results[0].email);
const {seed} = res.data.info;
return axios.get(`https://randomuser.me/api/?seed=${seed}`)
})
.then((res) => {
console.log(res.data.results[0].email);
})
.catch((err) => {
console.log(err);
});
```
<br>
### 如何撰寫==同時發出==多個請求
- Promise的`.all()`方法,可**同時發出**多個請求。
- 在`.all()`方法裡代入**陣列**,**可放入多個axios請求**。
- 同時發出多個請求,==接收資料也會收到多筆資料(陣列形式)==,接收資料同樣用`.then()`。
:::success
資料全部接收到才會執行`.then()`
:::

- **同時發出**多個請求,若==其中一個請求出錯==或==其中一個請求沒有回傳資料==,最後`.then()`接收的資料==也會全部出錯==。

```javascript=
Promise.all([
axios.get('https://randomuser.me/api/'),
axios.get('https://randomuser.me/ap/'),
axios.get('https://randomuser.me/api/')
])
.then((arrayRes) => {
console.log(arrayRes);
})
.catch((err) => {
console.dir(err);
})
```
#### Promise.allSettled()
- Promise的`.allSettled()`方法,與`.all()`一樣,**也可同時**發出多個請求。
- 與`.all()`最大差異
:::success
**同時發出**多個請求,不管有沒有==請求出錯==或==請求沒有回傳資料==,最後`.then()`==都會接收到資料==。
最後接收資料的資料(物件) ,每一筆都多了**status**的屬性,確認狀態。如果請求出錯**status**的屬性值會是`rejected`。
:::

```javascript=
Promise.allSettled([
axios.get('https://randomuser.me/api/'),
axios.get('https://randomuser.me/ap/'), // api位置錯誤
axios.get('https://randomuser.me/api/')
])
.then((arrayRes) => {
console.log(arrayRes);
})
```