Node.js自學筆記 (3/12)

  • fetch只能使用於javascript前端,不能使用於node.js後端
  • locals是在response裡面的子物件

Promise (此為JavaScript ES6的一個核心功能)

Promise 是在ES6裡面的新增功能。
Promise 是一個表示非同步運算的最終完成或失敗的物件。由於多數人使用預建立的 Promise,這個導覽會先講解回傳 Promise 的使用方式,之後再介紹如何建立。

基本上,一個 Promise 是一個根據附加給他的 Callback 回傳的物件,以取代傳遞 Callback 到這個函數。

舉例來說,下方的範例若用舊方式應該會有兩個 Callback,並根據成功或失敗來決定使用哪個:

function successCallback(result) {
  console.log("It succeeded with " + result);
}

function failureCallback(error) {
  console.log("It failed with " + error);
}

doSomething(successCallback, failureCallback);

而新作法會回傳一個 Promise,這樣你就可以附加 Callback:

let promise = doSomething(); 
promise.then(successCallback, failureCallback);

再簡單點:

doSomething().then(successCallback, failureCallback);

我們稱之為 非同步函數呼叫 。這個做法有許多好處。 ref: https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Using_promises

一個 Promise 有這些保證:

  • Callback 不會在當次的迴圈運行結束前呼叫。
  • Callback 用 .then 添加,在非同步運算結束呼叫,像前面那樣。
  • 複 Callback 可以透過重複呼叫 .then 達成。

但 Promise 主要的立即好處是串連。

Promise可以解決所謂callback hell的開發情況。callback hell如下圖所示:


寫法沒有問題,但是會對之後的維護造成困擾。

一個簡單的premise範例:

new Promise((resolve, reject) => {
    console.log('Initial');

    resolve();
})
.then(() => {
    throw new Error('Something failed');
        
    console.log('Do this');
})
.catch(() => {
    console.log('Do that');
})
.then(() => {
    console.log('Do this whatever happened before');
});

promise主要是做非同步(Async)的事情,若是要做同步(Sync)的事情就不需要使用promise。promise是我要做一件事情,但是這個事情不知道甚麼時候做完,我可能需要等待,我就會把處理的順序動作放在promise裡面。當promise成功完成,就呼叫resolve();,然後進入到 .then(() => {}),如果發生錯誤,就呼叫reject,然後進入到.catch(() => {})

範例一

new Promise((resolve, reject) => {
    setTimeout(function () {
        resolve('Hello');
        // reject('bad');
    }, Math.random() * 1000);

})
    .then(result => {
        info.innerHTML += `${result} <br>`;
        return 'hi';
    })
    .then(a => {
        // throw new Error('abc');
        info.innerHTML += `a: ${a} <br>`;
    })
    .catch(ex => {
        info.innerHTML += `ex: ${ex.toString()} <br>`;
    })
    .then(b => {
        info.innerHTML += `b: ${b} <br>`;
    })

範例二

const p = new Promise((resolve, reject)=>{
        setTimeout(function(){
            reject('hello');
        }, 300);
    });

    console.log(1);
    p.then(result=>{
        console.log('then:', result);
        return 3;
    })
    .then(a=>{
        console.log(`a: ${a}`);
    })
    .then(b=>{
        console.log(`b: ${b}`);
    })
    .catch(error=>{
        console.log(`error: ${error}`);
        return 'from error';
    })
    .then(c=>{
        console.log(`c: ${c}`);
    })
    console.log(2);

範例三

    const p = new Promise((resolve, reject)=>{
        setTimeout(function(){
            resolve('hello');
        }, 300);
    });

    console.log(1);
    p.then(result=>{
        console.log('then:', result);
        return new Promise((resolve, reject)=>{
            setTimeout(function(){
                resolve('hihi');
            }, 300);
        });
    })
    .then(a=>{
        throw new Error('bad');
        console.log(`a: ${a}`);
    })
    .catch(error=>{
        console.log(`error: ${error}`);
    })

    console.log(2);

範例四

把要做的事情包成一個function,做完之後的結果return一個promise

    const myFunc = (msec)=>{
        return new Promise((resolve, reject)=>{
            setTimeout(function(){
                resolve(Math.random()*100);
            }, msec);
        });
    };

    const data = [];

    myFunc(1300)
        .then(r=>{
            data.push(r);
            info.innerHTML += `${r} <br>`;
            return myFunc(1400);
        })
        .then(r=>{
            data.push(r);
            info.innerHTML += `${r} <br>`;
            return myFunc(1200);
        })
        .then(r=>{
            data.push(r);
            info.innerHTML += `${r} <br>`;
            info.innerHTML += JSON.stringify(data);
        })

若需要明確界定做作之間的順序相依性,就要使用promise。若不在乎何時做完,也不在乎某function之前要先完成哪些function,就不需要使用promise,因為就讓functions各自去執行即可。

通常使用promise時,會搭配使用箭頭函式,因為promise箭頭函式都同樣是ES6的新功能,可以使用promise就代表可以使用箭頭函式了。

範例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="info">
        <p>123</p>
    </div>
    <script>
        function func(){
            return new Promise((resolve, reject)=>{
                setTimeout(function(){
                    resolve(Math.random())
                }, 500)
            })
        }

        func()
        .then(r=>{
            info.innerHTML += `<p>${r}</p>`;
        })
    </script>
</body>
</html>

另外也可以再使用 return func() ,再呼叫promise一次,範例如下:

        function func(){
            return new Promise((resolve, reject)=>{
                setTimeout(function(){
                    resolve(Math.random())
                }, 500)
            })
        }

        func()
        .then(r=>{
            info.innerHTML += `<p>${r}</p>`;
            return func();
        })
        .then(r=>{
            info.innerHTML += `<p>${r}</p>`;
        })

回到之前講的 fetch 的範例,程式如下:

const data={
            email: $('#email').val(),
            password: password.value
        }
        fetch('/try-json-post', {
            method: 'POST',
            headers: {
                'content-type':'application/json'
            },
            body: JSON.stringify(data)
        })
        .then(r=>r.text())
        .then(txt=>{
            info.innerHTML=txt;
        })

其中,r=>r.text()單行的箭頭函式,表示 return r.text()

不可以寫成下面的樣子:

r=>{
    r.text()
}

這樣沒有return的效果。

如果不是單行的箭頭函式,return要自己寫。如下:

r=>{
    return r.text()
}

Fetch APIResponse 介面代表了一個請求會返回的回應。

你可以用 Response.Response() 建構子創建一個新的 Response物件。但實務上碰到 Response 物件的時機,比較常出現在進行了一個 API 操作後,得到返回的 Response 物件。舉例而言,使用 service worker Fetchevent.respondWith 或是使用單純的GlobalFetch.fetch()

上一個例子中,.then(r=>r.text())r=>r 即為Fetch API的response的物件。

介面是一種類型類別也是一種類型

類別具有實作功能;沒有實作功能,只用來定義方法叫做介面。(參考JavaScript的物件導向,JavaScript的物件導向屬於殘缺版,並不完整。)

Body.json()

Takes a Response stream and reads it to completion. It returns a promise that resolves with the result of parsing the body text as JSON.

Body.text()

Takes a Response stream and reads it to completion. It returns a promise that resolves with a USVString (text).

Body.json()Body.text()都是呼叫promise。