### JavaScript 非同步
###### Ajax, Fetch, RxJS
----
#### 課程聊天室:https://tlk.io/mrtjs

----
### 環境建置
0. 打開 vscode 並利用 `ctrl+j` 打開命令列視窗
1. 在命令列執行利用 npm 安裝 `json-server`
`npm install -g json-server`
2. 在命令列執行利用 npm 安裝 `lite-server`
`npm install --global lite-server`
3. 創建資料夾 js-async
----
4. 在 js-async 創建檔案 `db.json`
```json
{
"posts": [
{ "id": 1, "title": "json-server", "author": "typicode" }
],
"comments": [
{ "id": 1, "body": "some comment", "postId": 1 }
],
"profile": { "name": "typicode" }
}
```
5. 在 js-async 創建檔案 `bs-config.json` 並貼上以下檔案
```json
{
"port": 8000
}
```
----
6. 在 js-async 創建檔案 `index.html` 並貼上以下檔案
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>
// write your code below
</script>
<script>
</script>
<title>Document</title>
</head>
<body>
</body>
</html>
```
----
7. 在命令列執行
`json-server --watch db.json`
8. 點擊分割視窗按鈕

9. 在命令列執行
`lite-server`
---
### Ajax
###### asynchronous javascript and xml
###### 泛指瀏覽器提供的非同步功能
----
###### XMLHttpRequest - GET HTML
```javascript=1
function reqListener (e) {
console.log(e.target.responseText);
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "https://hackmd.io/");
oReq.send();
```
----
###### XMLHttpRequest - GET JSON
```javascript=1
function reqListener (e) {
console.log(e.target.responseText);
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "http://localhost:3000/posts");
oReq.send();
```
----
###### XMLHttpRequest - POST JSON
```javascript=1
function reqListener (e) {
console.log(e.target.responseText);
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
// 準備資料
var data = {};
data.title = 'The Adventures of Tom'
data.author = 'Mark Twain'
oReq.open("POST", "http://localhost:3000/posts");
oReq.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
oReq.send(JSON.stringify(data));
```
----
###### XMLHttpRequest - PUT JSON
```javascript=1
function reqListener (e) {
console.log(e.target.responseText);
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
// 準備資料
var data = {};
data.title = 'The Adventures of Tom - updated'
data.author = 'Mark Twain'
oReq.open("PUT", "http://localhost:3000/posts/1");
oReq.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
oReq.send(JSON.stringify(data));
```
----
###### XMLHttpRequest - DELETE
```javascript=1
function reqListener (e) {
console.log(e.target.responseText);
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("DELETE", "http://localhost:3000/posts/1");
oReq.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
oReq.send();
```
----
### Exercise
> ###### 請於新增單一則 post 完成並收到回應後,再發出拿到所有 post 的請求,當拿到貼文後,再發出刪除最後一篇貼文的請求
----
```javascript=1
function reqListener () {
var oReq2 = new XMLHttpRequest();
oReq2.addEventListener("load", function(){
var oReq3 = new XMLHttpRequest();
oReq3.addEventListener("load", function(e){
console.log(e.target.responseText);
});
oReq3.open("DELETE", "http://localhost:3000/posts/5");
oReq3.send();
});
oReq2.open("GET", "http://localhost:3000/posts");
oReq2.send();
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
var data = {};
data.title = 'The Adventures of Tom'
data.author = 'Mark Twain'
oReq.open("POST", "http://localhost:3000/posts");
oReq.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
oReq.send(JSON.stringify(data));
```
---
JQuery Ajax
----

----
> ###### The $.ajax() function underlies all Ajax requests sent by jQuery. It is often [unnecessary]() to directly call e.target function, as several higher-level alternatives like $.get() and .load() are available and are easier to use. If less common options are required, though, $.ajax() can be used more flexibly.
> ...
----
> ...
> ###### The jqXHR Object - The jQuery XMLHttpRequest (jqXHR) object returned by $.ajax() as of jQuery 1.5 is a [superset of the browser's native XMLHttpRequest object](). For example, it contains responseText and responseXML properties, as well as a getResponseHeader() method. When the transport mechanism is something other than XMLHttpRequest (for example, a script tag for a JSONP request) the jqXHR object simulates native XHR functionality where possible.
> [name=[jQuery ajax](https://api.jquery.com/jquery.ajax/)]
----
###### Jquery ajax - GET JSON
```javascript=1
// 寫法 1
$.ajax({
method: 'GET',
url: 'http://localhost:3000/posts'
})
.done(function(value) {
console.log('success', value);
})
.fail(function(value) {
console.log('error', value);
})
.always(function(value) {
console.log('complete', value);
})
// 寫法 2
$.get('http://localhost:3000/posts')
.done(function(value) {
console.log('success', value);
})
.fail(function(value) {
console.log('error', value);
})
.always(function(value) {
console.log('complete', value);
});
// 寫法 3
$.get( "http://localhost:3000/posts", function( value ) {
console.log('success', value);
});
```
----
###### Jquery ajax - POST JSON
```javascript=1
// 寫法 1
$.ajax({
method: 'POST',
url: 'http://localhost:3000/posts',
data: {
title: 'The Adventures of Tom',
author: 'Mark Twain'
}
})
.done(function(value) {
console.log('success', value);
})
.fail(function(value) {
console.log('error', value);
})
.always(function(value) {
console.log('complete', value);
});
// 寫法 2
$.post('http://localhost:3000/posts', {
title: 'The Adventures of Tom',
author: 'Mark Twain'
})
.done(function(value) {
console.log('success', value);
})
.fail(function(value) {
console.log('error', value);
})
.always(function(value) {
console.log('complete', value);
});
// 寫法 3
$.post(
'http://localhost:3000/posts',
{
title: 'The Adventures of Tom',
author: 'Mark Twain'
},
function(value) {
console.log('success', value);
}
);
```
----
###### Jquery ajax - PUT
```javascript=1
$.ajax({
method: 'PUT',
url: 'http://localhost:3000/posts/3',
data: {
title: 'The Adventures of Tom3',
author: 'Mark Twain3'
}
})
.done(function(value) {
console.log('success', value);
})
.fail(function(value) {
console.log('error', value);
})
.always(function(value) {
console.log('complete', value);
});
```
----
###### Jquery ajax - DELETE
```javascript=1
$.ajax({
method: 'DELETE',
url: 'http://localhost:3000/posts/1',
})
.done(function(value) {
console.log('success', value);
})
.fail(function(value) {
console.log('error', value);
})
.always(function(value) {
console.log('complete', value);
});
```
----
### Exercise
> ###### 請於新增單一則 post 完成並收到回應後,再發出拿到所有 post 的請求,當拿到貼文後,再發出刪除最後一篇貼文的請求
----
> ###### post 完成,拿到新增貼篇的 id ,再由此 id 發出 request 拿到評論,再發出 request 拿到個 like ......
----
### 巢狀 callback 問題 - [callback hell](https://medium.com/front-end-weekly/one-promise-to-rule-them-all-f1eb7f35603a)

---
Promise
> ###### 非同步運算的最終完成或失敗的物件
----
```javascript=1
function myAsyncFunction(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open("GET", url)
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = () => reject(xhr.statusText)
xhr.send()
});
}
new myAsyncFunction('http://localhost:3000/posts/1')
.then(function(e){
console.log(e);
return new myAsyncFunction('http://localhost:3000/posts/2')
})
.then(function(e){
console.log(e);
return new myAsyncFunction('http://localhost:3000/posts/3')
})
.catch(function(e){
console.log(e);
});
```
----
### 使用方式
1. 經由 then 呼叫成功後的邏輯
2. 經由 catch 呼叫失敗後的邏輯
3. 每一個 then 會回傳新的 promise
4. catch 會抓住錯誤,並執行一次錯誤邏輯
----
### 特性分析
1. ES6
2. 多種 api, library 實作
3. 從水平延伸走向縱向延伸
4. 解決 callback hell
5. 解決 callback hell 的錯誤處理問題
6. 大部分開發者不需要自訂 Promise 如何實作,而是使用建立好的 Promise
----
[fetch](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch)
> ###### Fetch API 提供了工具使操作 http pipeline 更加容易, 像是日常會用到的發送和接送資料都可以使用。並且有 global 的 fetch() 可以直接呼叫, 使開發能夠用更簡潔的語法取得非同步資料。
----
1. fetch() 回傳的 promise 物件, 404, 500 -> resolve
2. fetch 預設上不傳送或接收任何 cookies,需要使用 cookies 必須額外設定 credentials。
----
fetch get
```javascript=1
fetch('http://localhost:3000/posts')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
```
----
fetch post
```javascript=1
postData('http://localhost:3000/posts', {
title: 'The Adventures of Tom',
author: 'Mark Twain'
})
.then(data => console.log(data)) // JSON from `response.json()` call
.catch(error => console.error(error))
function postData(url, data) {
// Default options are marked with *
return fetch(url, {
body: JSON.stringify(data), // must match 'Content-Type' header
cache: 'no-cache', // 決定 request 的 cache 機制 *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', //決定是否送出憑證 include, same-origin, *omit
headers: {
'content-type': 'application/json'
},
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // 是否跨域, no-cors, cors, *same-origin
redirect: 'follow', // 決定如 status code 301,302 發生時,處理邏輯 manual, *follow, error
referrer: 'no-referrer', // 發出請求時的位置 *client, no-referrer
})
.then(response => response.json()) // 輸出成 json
}
```
----
fetch put
```javascript=1
putData('http://localhost:3000/posts/1', {
title: 'The Adventures of Tom1',
author: 'Mark Twain1'
})
.then(data => console.log(data)) // JSON from `response.json()` call
.catch(error => console.error(error))
function putData(url, data) {
// Default options are marked with *
return fetch(url, {
body: JSON.stringify(data), // must match 'Content-Type' header
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, same-origin, *omit
headers: {
'content-type': 'application/json'
},
method: 'PUT', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, cors, *same-origin
redirect: 'follow', // manual, *follow, error
referrer: 'no-referrer', // *client, no-referrer
})
.then(response => response.json()) // 輸出成 json
}
```
----
fetch delete
```javascript=1
deleteData('http://localhost:3000/posts/2')
.then(data => console.log(data)) // JSON from `response.json()` call
.catch(error => console.error(error))
function deleteData(url, data) {
// Default options are marked with *
return fetch(url, {
method: 'DELETE', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, cors, *same-origin
})
.then(response => response.json()) // 輸出成 json
}
```
----
### Exercise - fetch 練習
> ###### 請於新增單一則 post 完成並收到回應後,再發出拿到所有 post 的請求,當拿到貼文後,再發出刪除最後後一篇貼文的請求
----
### Exercise - fetch 練習
> ###### 請嘗試更新 id 為 100 的貼文,完成並收到回應後,再發出拿到所有 post 的請求,如果發生錯誤請使用console.error() 抓住錯誤
---
RxJS
----
### [rx - reactive programming](http://reactivex.io/)

----
### Observer pattern
###### [監聽資料流的變化,推送給訂閱者](https://weihanglo.tw/posts/2017/intro-rx-0-reactivex/)
----
### [Iterator pattern](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators)
###### 遍歷資料、資料流結束、錯誤發生
```javascript=1
function makeRangeIterator(start = 0, end = Infinity, step = 1) {
let nextIndex = start;
let iterationCount = 0;
const rangeIterator = {
next: function() {
let result;
if (nextIndex < end) {
result = { value: nextIndex, done: false }
nextIndex += step;
iterationCount++;
return result;
}
return { value: iterationCount, done: true }
}
};
return rangeIterator;
}
let it = makeRangeIterator(1, 10, 2);
let result = it.next();
while (!result.done) {
console.log(result.value); // 1 3 5 7 9
result = it.next();
}
console.log("Iterated over sequence of size: ", result.value);
```
----
### [functional programming](https://blog.jerry-hong.com/series/fp/think-in-fp-02/)
1. 一等公民 (First-Class) - function 可以被當成參數傳入 function
2. 引用透明 (Referential Transparency) - 相同的參數,相同的 output
----
### RxJS 範例 - version 6
###### 使用 [StackBlitz](https://stackblitz.com/)
```javascript=1
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
const observable = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 1000);
});
console.log('just before subscribe');
const observer = {
next: function(x) { console.log('got value ' + x); },
error: function(err) { console.error('something wrong occurred: ' + err); },
complete: function() { console.log('done'); }
}
const subscription = observable
.pipe(map(item => (+item + 1)))
.subscribe(observer);
console.log('just after subscribe');
```
----
### Exercise
> ###### 請接續上一範例,不要讓 observable 在 got value 5 的時候結束,延後一秒,產生 got value 6 ,再結束
----
[核心概念](https://www.slideshare.net/WillHuangTW/angular-2-advanced-topic-rxjs)
1. Observable - 可觀察的物件
2. Observer - 觀察者物件
3. Subscription - 訂閱物件
4. Operators - 運算子
5. Subject - 廣播資料傳個多個 observer
6. Schedulers - 控制事件併發狀況
----
###### 發出 request
```javascript=1
import { ajax } from 'rxjs/ajax';
import { map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
const obs$ = ajax(`http://localhost:3000/posts`).pipe(
map(userResponse => userResponse.response),
catchError(error => {
console.log('error: ', error);
return of(error);
})
);
obs$.subscribe(
val => console.log(val)
)
```
----
###### 合併多個訊號源
```javascript=1
import { merge, interval } from 'rxjs';
import { take, tap } from 'rxjs/operators';
const timer1 = interval(3000).pipe(tap(val => console.log('timer1'), take(3)));
const timer2 = interval(5000).pipe(tap(val => console.log('timer2'), take(2)));
const merged = merge(timer1, timer2);
merged.subscribe(x => console.log(x));
```
----
###### 合併多個訊號源,並控制 response 順序
```javascript=1
import { of, concat, merge } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { map, catchError, delay } from 'rxjs/operators';
const obs$ = ajax(`http://localhost:3000/comments`).pipe(
delay(1000),
map(userResponse => userResponse.response),
catchError(error => {
console.log('error: ', error);
return of(error);
})
);
const obs1$ = ajax(`http://localhost:3000/profile`).pipe(
map(userResponse => userResponse.response),
catchError(error => {
console.log('error: ', error);
return of(error);
})
);
const obs2$ = ajax(`http://localhost:3000/posts`).pipe(
map(userResponse => userResponse.response),
catchError(error => {
console.log('error: ', error);
return of(error);
})
);
const merged = concat(obs$, obs1$, obs2$);
merged.subscribe(x => console.log(x));
```
----
###### ajax post
```javascript=1
import { ajax } from 'rxjs/ajax';
import { of } from 'rxjs';
const users = ajax({
url: 'https://httpbin.org/delay/2',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: {
rxjs: 'Hello World!'
}
}).pipe(
map(response => console.log('response: ', response)),
catchError(error => {
console.log('error: ', error);
return of(error);
})
);
```
----
###### 單一訊號源多個訂閱
```javascript=1
import { merge, interval, timer } from 'rxjs';
import { take, tap } from 'rxjs/operators';
const timer1 = interval(2000).pipe(take(3));
timer1.subscribe(x => console.log(x));
setTimeout(function() {
timer1.subscribe(x => console.log(`%c三秒後 ${x}`,"color: red"));
}, 2500);
```
> ###### 後來的訂閱從資料源頭開始
----
### subject
```javascript=1
import { Subject, interval } from 'rxjs';
const subject = new Subject();
interval(2000).subscribe(val => subject.next(val));
subject.subscribe({
next: (v) => console.log(`%cobserverA: ${v}`, 'color: blue')
});
setTimeout(() => {
subject.subscribe({
next: (v) => console.log(`%cobserverB: ${v}`, 'color: red')
});
}, 4000);
```
> ###### 後來的訂閱從最新的資料狀態開始
----
### Scheduler - 訂閱排程管理
```javascript=1
import { Observable, asyncScheduler } from 'rxjs';
import { observeOn } from 'rxjs/operators';
const observable = new Observable((observer) => {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
}).pipe(
observeOn(asyncScheduler)
);
console.log('just before subscribe');
observable.subscribe({
next(x) {
console.log('got value ' + x)
},
error(err) {
console.error('something wrong occurred: ' + err);
},
complete() {
console.log('done');
}
});
console.log('just after subscribe');
```
----
###### [Scheduler 類型](https://blog.kevinyang.net/2018/08/31/rxjs-scheduler/)
1. queue
2. asap
3. async
4. [animationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame)
----
Ref:
1. [Using XMLHttpRequest
](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest)
2. [callback hell](https://medium.com/front-end-weekly/one-promise-to-rule-them-all-f1eb7f35603a)
3. [All You Need Is Function](https://blog.jerry-hong.com/series/fp/think-in-fp-02/)
4. [前端工程研究:理解函式編程核心概念與如何進行 JavaScript 函式編程](https://blog.miniasp.com/post/2016/12/10/Functional-Programming-in-JavaScript)
5. [reactivex](http://reactivex.io/)
6. [希望是最淺顯易懂的 RxJS 教學](https://blog.techbridge.cc/2017/12/08/rxjs/)
7. [MDN - Iterators and Generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators)
8. [Rx 入門零:ReactiveX](https://weihanglo.tw/posts/2017/intro-rx-0-reactivex/)
9. [Angular 2 開發實戰:進階開發篇 - RxJS 新手入門](https://www.slideshare.net/WillHuangTW/angular-2-advanced-topic-rxjs)
10. [RxJS Scheduler](https://blog.kevinyang.net/2018/08/31/rxjs-scheduler/)
----
# JavaScript Async
[範例練習](https://hackmd.io/@ChadZ/JS-MRT-4-EXERCISE)
{"metaMigratedAt":"2023-06-15T02:54:50.310Z","metaMigratedFrom":"YAML","title":"JavaScript Async","breaks":true,"slideOptions":"{\"title\":\"JavaScript 非同步\",\"theme\":\"night\",\"transition\":\"slide\",\"spotlight\":{\"enabled\":false},\"transitionSpeed\":\"fast\"}","contributors":"[{\"id\":\"7da0a1a8-6add-40a7-9acc-950070ff069c\",\"add\":25842,\"del\":7076}]"}