---
# System prepended metadata

title: JavaScript Async
tags: [' MRT', Course]

---

---
slideOptions:        # 簡報相關的設定
  title: JavaScript 非同步
  theme: night   # 顏色主題
  transition: 'slide' # 換頁動畫
  spotlight:
      enabled: false
  transitionSpeed: fast
tags: Course, MRT
---

### JavaScript 非同步
###### Ajax, Fetch, RxJS

----

#### 課程聊天室：https://tlk.io/mrtjs

![課程聊天室](https://i.imgur.com/R9qiCXX.png)

----

### 環境建置
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. 點擊分割視窗按鈕
![](https://i.imgur.com/wzA7OSD.png)
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

----

![jqXHR](https://i.imgur.com/Yj6R65l.png)

----

> ###### 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)
![callback hell](https://i.imgur.com/Nmcy0hM.jpg)

---

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/)
![](https://i.imgur.com/emHm392.png)

----

### 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)