[ 筆記 ] Fetch 與 Promise


Posted by krebikshaw on 2020-09-07

api 程式碼 (置頂)

fetch(url, {
  method: 'POST',
  body: JSON.stringify(data),
  headers: new Headers({
    'Content-Type': 'application/json'
  })
}).then(res => res.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));

初探 Fetch

當我們在 JavaScript 使用 fetch() 函式傳入一段 url,它會 return 一個 promise ,這個 promise 是一個獨特的物件。

const result = fetch('https:......')
console.log(result)  // Promise {<pending>}

要拿到 Promise 裡面的資料,我們需要使用 .then() 這個函式,並傳入一個 callback function

fetch('https:......').then(response => {
  console.log(response.status); // 200
})

成功從 Promise 拿到資料後,我們希望取得 response.body 裡的文字,我們需要使用 text() 這個函式

fetch('https:......').then(response => {
  console.log(response.text());  // Promise
})

會發現 response.text() 會 return 一個 Promise 物件,我們已經知道要從 Promise 裡拿資料要用 .then(),於是可以把程式碼改成

fetch('https:......').then(response => {
  response.text().then(text => {
    console.log(text);  // 成功拿到 response.body 的 json 資料
  })
})

但是我們希望把 body 裡的 json 資料轉換回物件,可以用 json() 這個函式(他跟 JSON.parse() 的效果是一樣的),但是這個函式一樣會 return 一個 Promise 物件,所以我們要用 .then 傳入一個 callback function 來印出資料

fetch('https:......').then(response => {
  response.json().then(text => {
    console.log(text);  // 拿到 response.body 轉成的物件
  })
})

.then() 神奇的地方

如果在 .then() 裡面回傳一個值,這個值就會成為下一個 .then() 的參數

fetch().then(test => {
  return 'Hello'; // 在 .then() 裡面回傳 'Hello' 出去
}).then(str => {
  // 下一個 .then() 中的 str 就會是剛剛 return 的 'Hello'
  console.log(str);    // Hello
})

如果在 .then() 裡面回傳一個 Promise,它會幫你把這個 Promise 解析完之後,傳給下一個 .then()

fetch('https:......').then(response => {
  return response.json() //  回傳一個 Promise
}.then(text => {
  console.log(text);  // 拿到 response.body 轉成的物件
})

.catch() 錯誤處理

對於 fetch() 來說,錯誤的意思是要連 response 都拿不到才會叫做錯誤,如果只是 400 ERROR 回傳了一個 response.status 這個不是錯誤,只要有拿到 response 都不是錯誤。

  • 因為 fetch() 是非同步的,所以要做錯誤處理,需要在最後加一個 .catch() 去接住它的 error
fetch('https:......').then(response => {
  return response.json();
}.then(text => {
  console.log(text);
}).catch(err => {
  console.log(err);
})
  • 這種 .catch() 的方式,跟 try{ ... }.catch(e){ ... } 是完全不同的,這邊的方式是非同步的,try catch 處理的是同步的資料

POST method

參考網站:https://developer.mozilla.org/zh-TW/docs/Web/API/Fetch_API/Using_Fetch

  • fetch() 的第一個參數是 url,第二個參數放一個物件,寫法如下:
fetch(url, {
  method: 'POST',
  body: JSON.stringify(data), // 資料可以是字串或物件
  headers: new Headers({  // 要設定 header 要 new 一個物件
    'Content-Type': 'application/json'
  })
}).then(res => res.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));

fetch 的使用注意事項

  • Content-Type 裡面放的東西,需要對應到 body 的內容,要先去查看 server 接收的是什麼樣的資料,才來決定你的 Content-Type 是什麼
  • 如果是 cross origin 跨網域發送的 api,fetch 方法在預設條件下是不會帶 cookie 上去的,所以如果要使用 fetch 在跨網域的情況下發送帶有 cookie 的 api,需要加上一行 credentials: 'include' 指令來設定:
fetch(url, {
  method: 'POST',
  body: JSON.stringify(data),
  headers: new Headers({
    'Content-Type': 'application/json'
  }),
  credentials: 'include'  // 加上這行指令
}).then(res => res.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));
  • mode: 'no-cors' 這行指令的設定並不是說能夠突破 server cors 的限制,那是不可能的。這行指令只是在告訴瀏覽器:我明白 server 有做 cors 的設定,我本來就沒有要拿到 response,所以請你不必跳錯誤訊息給我。 (瀏覽器就不會跳出錯誤訊息,但是會給你一個空的 response 表示這段 api 的發送是有成功的)

自製一個 Promise

  • 我們可以 new 一個 Promise 傳入一個 function,這個 function 要做為這個 Promise 的設定
function init(resolve, reject) {  // resolve, reject 是兩個 function ,用來決定這個 Promise 裡的資料內容
  resolve('Hello');  //  當需要回傳資料的時候,就 call resolve
  reject('error~~~');   //  當需要觸發錯誤的時候,就 call reject
}

const myPromise = new Promise(init);

myPromise.then(data => {
  console.log(data)  //  Hello
}).catch(err => {
  console.log(err)   //  error~~~
})

簡化一點,會寫成下面這樣:

const myPromise = new Promise((resolve, reject) => {
  resolve('Hello');
  reject('error~~~');
});

XHR 包成 Promise 範例

const myPromise = new Promise((resolve, reject) => {
  var request = new XMLHttpRequest();
  request.open('GET', '/my/url', true);

  request.onload = function() {
    if (this.status >= 200 && this.status < 400) {
      let data = JSON.parse(this.response);
      resolve(data);
    }
  };

  request.onerror = function(err) {
    reject(err);
  };

  request.send();
});

myPromise.then(data => {
  console.log('myPromise data', data)
}).catch(err => {
  console.log('error', err)
})
  • new Promise 裡面可以放任何同步或非同步的事情,再利用 resolve, reject 來設定在 call promise 會拿到的資料

實作一個可以自訂睡眠時間的 Promise

function sleep(ms) {
  return new Promise(resolve => {
    setTimeOut(resolve, ms);
  })
}


sleep(1500).then(data => {  // 等過了 1500ms 後才會執行 .then() 的動作
  console.log(data);
})

上面那段程式碼,經過簡化後,可以變成一個非常簡短的樣子:

const sleep = ms => new Promise(resolve => setTimeOut(resolve, ms))

async 與 await

當我們想要用同步的寫法來寫 fetch,但是又要能達成非同步的功能,我們可以利用 async 與 await 來達成

// 非同步寫法:
function main() {
  console.log('開始')
  sleep(5000).then(() => {
    console.log('結束')
  })
}

//  同步寫法
async function main() {
  console.log('開始')
  await sleep(5000)
  console.log('結束')
}
  • async 代表著這個 function 是非同步的
  • await 後面接一個 Promise,就會等到這個 Promise 執行完畢,才會繼續下一行,但是因為是非同步的,所以整個畫面不會為了等那幾秒鐘被 block 住

實際 api 串接

const getData = () => fetch(https:....).then(response => response.json)

async function main() {
  let result 
  try {     // 這個時候就可以使用 try catch 的寫法了
    result = await getData()
  } catch(err) {
    console.log(err)
  }
  console.log(result)
}

main()

補充

製作 api 好用網站: Mocky


#fetch #Promise







Related Posts

第十一~十四週學習心得

第十一~十四週學習心得

內嵌 youtube 的廣告

內嵌 youtube 的廣告

[ 筆記 ] DOM - 事件傳遞機制:捕獲與冒泡、事件代理

[ 筆記 ] DOM - 事件傳遞機制:捕獲與冒泡、事件代理


Comments