【JS 口袋書】第 10 章:使用非同步 JavaScript

前端小智發表於2019-10-30

作者:valentinogagliardi

譯者:前端小智

來源:github


REST API 和 XMLHttpRequest

如果你問我,我會說 JS 挺強大的。作為一種在瀏覽器中執行的指令碼語言,它可以做所有類似的事情:

  • 動態建立元素
  • 新增互動性

等等。在第 8 章中,我們們從陣列開始構建了一個 HTML 表格。 硬編碼陣列是一個同步資料來源,也就是說,可以直接在我們們的程式碼中使用它,無需等待。 但是大多數時候,資料都是從後臺請求過來的。網路請求始終是非同步操作,而不是同步資料來源:請求資料,伺服器會有一定的延遲後才響應。

JS 本身沒有內建的非同步性:它是“宿主”環境(瀏覽器或 Node.j),為處理耗時的操作提供了外部幫助。在第3章中,我們們看到了setTimeoutsetInterval,這兩個屬於 Web API 的。瀏覽器提供了很多 API,其中還有一個叫XMLHttpRequest,專門用於網路請求。

事實上,它來自於以 XML 資料格式的時代。現在 JSON 是最流行的用於在 Web 服務之間移動資料的通訊“協議”,但 XMLHttpRequest 這個名稱最終被保留了下來。

XMLHttpRequest 也是 AJAX 技術的一部分,它是 “非同步JavaScript和XML” 的縮寫。AJAX 就是為了在瀏覽器中儘可能靈活地處理網路請求而誕生的。它的作用是能夠從遠端資料來源獲取資料,而不會導致頁面重新整理。當時這個想法幾乎是革命性的。隨著 XMLHttpRequest (大約13年前)的引入,我們們可以使用它來進行非同步請求。

var request = new XMLHttpRequest();

request.open('GET', "https://academy.valentinog.com/api/link/");

request.addEventListener('load', function() {
  console.log(this.response);
})

request.send();
複製程式碼

在上述的示例中:

  • 建立一個新的 XMLHttpRequest 物件

  • 通過提供方法和網址來開啟請求

  • 註冊事件監聽器

  • 傳送請求

XMLHttpRequest 是基於DOM事件的,我們們可以使用 addEventListeneronload 來監聽“load”事件,該事件在請求成功時觸發。對於失敗的請求(網路錯誤),我們們可以在“error”事件上註冊一個偵聽器:

var request = new XMLHttpRequest();

request.open("GET", "https://academy.valentinog.com/api/link/")

request.onload = function() {
  console.log(this.response)
}

request.onerror = function() {
  // 處理錯誤
}

request.send();
複製程式碼

有了這些知識,我們們就更好地使用 XMLHttpRequest

通過 XMLHttpRequest 請求資料,構建 HTML 列表

REST API 提取資料後,我們們將構建一個簡單的 HTML 列表。 新建一個名為 build-list.html 的檔案:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>XMLHttpRequest</title>
</head>
<body>

</body>
<script src="xhr.js"></script>
</html>
複製程式碼

接下來,在同一個資料夾中建立一個名為 xhr.js 的檔案。在這個檔案中,建立一個新的 XHR 請求:

"use strict";

const request = new XMLHttpRequest();
複製程式碼

上面的呼叫(建構函式方式)建立了一個 XMLHttpRequest 型別的新物件。與 setTimeout 等非同步函式相反,我們把回撥作為引數:

setTimeout(callback, 10000);

function callback() {
  console.log("hello timer!");
}
複製程式碼

XMLHttpRequest 基於 DOM 事件,處理程式回撥註冊在 onload 物件上。當請求成功時,load 事件觸發。

"use strict";

const request = new XMLHttpRequest();

request.onload = callback;

function callback() {
  console.log("Got the response!");
}
複製程式碼

註冊回撥之後,我們可以使用 open() 開啟請求。它接受一個 HTTP 方法

"use strict";

const request = new XMLHttpRequest();

request.onload = callback;

function callback() {
  console.log("Got the response!");
}

request.open("GET", "https://academy.valentinog.com/api/link/");
複製程式碼

最後,我們可以使用 send() 傳送實際的請求

"use strict";

const request = new XMLHttpRequest();

request.onload = callback;

function callback() {
  console.log("Got the response!");
}

request.open("GET", "https://academy.valentinog.com/api/link/");
request.send();
複製程式碼

在瀏覽器中開啟 build-list.html,在控制檯中會看到“Got the response!”,說明請求成功。如果你還記得第6章,每個常規 JS 函式都有一個對其宿主物件的引用。因為回撥在 XMLHttpRequest 物件中執行,所以可以通過 this.response 獲取伺服器返回的資料。

"use strict";

const request = new XMLHttpRequest();

request.onload = callback;

function callback() {
  // this refers to the new XMLHttpRequest
  // response is the server's response
  console.log(this.response);
}

request.open("GET", "https://academy.valentinog.com/api/link/");
request.send();
複製程式碼

儲存檔案並重新整理 build-list.html。在控制檯可以看到返回的資料,資料格式是字串,有兩種方法可以把它變成 JSON 格式:

  • 方法一:在 XMLHttpRequest 物件上配置響應型別

  • 方法二:使用 JSON.parse()

方法一

"use strict";

const request = new XMLHttpRequest();

request.onload = callback;

function callback() {
  // this refers to the new XMLHttpRequest
  // response is the server's response
  console.log(this.response);
}

// configure the response type
request.responseType = "json";
//

request.open("GET", "https://academy.valentinog.com/api/link/");
request.send();
複製程式碼

方法二 比較推薦,也符合我們們現在的程式設計習慣:

"use strict";

const request = new XMLHttpRequest();

request.onload = callback;

function callback() {
  const response = JSON.parse(this.response);
  console.log(response);
}

request.open("GET", "https://academy.valentinog.com/api/link/");
request.send();
複製程式碼

再次重新整理build-list.html,會看到一個 JS 物件陣列,每個物件都具有相同的結構:

[
  //
  {
    title:
      "JavaScript Engines: From Call Stack to Promise, (almost) Everything You Need to Know",
    url: "https://www.valentinog.com/blog/engines/",
    tags: ["javascript", "v8"],
    id: 3
  }
  //
]
複製程式碼

這次,我們們沒有像第8章那樣手工建立陣列,而是通過 REST API 介面請求資料。

使用 JS 構建 HTML 列表(和除錯類)

這裡我們們使用 ES6 類的方法來構建,還會使用私有類欄位(在撰寫本文時,Firefox不支援該欄位)。在編寫任何程式碼之前,都要思考一下,別人會“如何使用我的類”? 例如,另一個開發人員可以使用我們們的程式碼並通過傳入來呼叫該類:

  • 用於獲取資料的 URL

  • 要將列表附加到的 HTML 元素

    const url = "academy.valentinog.com/api/link/"; const target = document.body; const list = new List(url, target);

有了這些要求,我們們就可以開始編寫類程式碼了。目前,它應該接受建構函式中的兩個引數,並擁有一個獲取資料方法

class List {
  constructor(url, target) {
    this.url = url;
    this.target = target;
  }

  getData() {
    return "stuff";
  }
}
複製程式碼

軟體開發中的普遍觀點是,除非有充分的理由要做相反的事情,否則不能從外部訪問類成員和方法。 在 JS 中,除非使用模組,否則沒有隱藏方法和變數的原生方法(第2章)。 即使是 class 也不能倖免於資訊洩漏,但是有了私有欄位,就能大概率避免這類問題。 JS 私有類欄位的目前還沒有成標準,但大部分瀏覽器已經支援了,它用 # 來表示,重寫上面的類:

class List {
  #url;
  #target;

  constructor(url, target) {
    this.#url = url;
    this.#target = target;
  }

  getData() {
    return "stuff";
  }
}
複製程式碼

你可能不喜歡語法,但是私有類欄位可以完成其工作。 這種方式,我們就不能從外部訪問 urltarget

class List {
  #url;
  #target;

  constructor(url, target) {
    this.#url = url;
    this.#target = target;
  }

  getData() {
    return "stuff";
  }
}

const url = "https://academy.valentinog.com/api/link/";
const target = document.body;
const list = new List(url, target);

console.log(list.url); // undefined
console.log(list.target); // undefined
複製程式碼

有了這個結構,我們們就可以將資料獲取邏輯移到 getData 中。

"use strict";

class List {
  #url;
  #target;

  constructor(url, target) {
    this.#url = url;
    this.#target = target;
  }

  getData() {
    const request = new XMLHttpRequest();
    request.onload = function() {
      const response = JSON.parse(this.response);
      console.log(response);
    };

    request.open("GET", this.#url);
    request.send();
  }
}

const url = "https://academy.valentinog.com/api/link/";
const target = document.body;
const list = new List(url, target);
複製程式碼

現在,為了顯示資料,我們們在 getData 之後新增一個名為 render 的方法。render 將為我們建立一個 HTML 列表,從作為引數傳遞的陣列開始:

"use strict";

class List {
  #url;
  #target;

  constructor(url, target) {
    this.#url = url;
    this.#target = target;
  }

  getData() {
    const request = new XMLHttpRequest();
    request.onload = function() {
      const response = JSON.parse(this.response);
      console.log(response);
    };

    request.open("GET", this.#url);
    request.send();
  }

  // The new method
  render(data) {
    const ul = document.createElement("ul");
    for (const element of data) {
      const li = document.createElement("li");
      const title = document.createTextNode(element.title);
      li.appendChild(title);
      ul.appendChild(li);
    }
    this.#target.appendChild(ul);
  }
}
複製程式碼

注意 document.createElement()document.createTextNode()appendChild()。我們們在第8章講DOM 操作的時候見過。this.#target 私有欄位將 HTML 列表附加到 DOM。現在,我想:

  • 獲取 JSON 響應後呼叫 render

  • 當使用者建立一個新的列表“例項”時立即呼叫 getData

為此,我們們在 request.onload 回撥內部呼叫 render

getData() {
  const request = new XMLHttpRequest();
  request.onload = function() {
    const response = JSON.parse(this.response);
    // Call render after getting the response
    this.render(response);
  };

  request.open("GET", this.#url);
  request.send();
}
複製程式碼

另一方面,getData 應該在建構函式中執行:

constructor(url, target) {
  this.#url = url;
  this.#target = target;
  // Call getData as soon as the class is used
  this.getData();
}
複製程式碼

完整程式碼:

"use strict";

class List {
  #url;
  #target;

  constructor(url, target) {
    this.#url = url;
    this.#target = target;
    this.getData();
  }

  getData() {
    const request = new XMLHttpRequest();
    request.onload = function() {
      const response = JSON.parse(this.response);
      this.render(response);
    };

    request.open("GET", this.#url);
    request.send();
  }

  render(data) {
    const ul = document.createElement("ul");
    for (const element of data) {
      const li = document.createElement("li");
      const title = document.createTextNode(element.title);
      li.appendChild(title);
      ul.appendChild(li);
    }
    this.#target.appendChild(ul);
  }
}

const url = "https://academy.valentinog.com/api/link/";
const target = document.body;
const list = new List(url, target);
複製程式碼

嘗試一下:在瀏覽器中重新整理 build-list.html 並檢視控制檯

Uncaught TypeError: this.render is not a function
複製程式碼

this.render 不是函式! 會是什麼呢? 此時,你可能想要達到第6章或更高版本,可以除錯程式碼。 在 getData中的 this.render(response) 之後,新增 debugger 指令:

getData() {
  const request = new XMLHttpRequest();
  request.onload = function() {
    const response = JSON.parse(this.response);
    debugger;
    this.render(response);
};

request.open("GET", this.#url);
  request.send();
}
複製程式碼

debugger 加了一個所謂的斷點,執行將停止在那裡。現在開啟瀏覽器控制檯並重新整理build-list.html。下面是將在 Chrome 中看到的:

仔細檢視“Scopes”選項卡。getData 中確實有一個 this,但它指向 XMLHttpRequest。 換句話說,我們試圖在錯誤的物件上訪問 this.render

為什麼 this 不匹配? 這是因為傳遞給 request.onload 的回撥在 XMLHttpRequest 型別的宿主物件中執行,呼叫 const request = request = new XMLHttpRequest() 的結果。解決方法,在前幾章中已經提到過了,可以使用 箭頭函式

  getData() {
    const request = new XMLHttpRequest();
    // The arrow function in action
    request.onload = () => {
      const response = JSON.parse(this.response);
      debugger;
      this.render(response);
    };

    request.open("GET", this.#url);
    request.send();
  }
複製程式碼

重新整理 build-list.html 並檢查它

Uncaught SyntaxError: Unexpected token u in JSON at position 0
複製程式碼

很好,前面的錯誤消失了,但是現在 JSON.parse 出現了一個問題。我們很容易想象它與 this 有關。將debugger 向上移動一行

  getData() {
    const request = new XMLHttpRequest();
    request.onload = () => {
      debugger;
      const response = JSON.parse(this.response);
      this.render(response);
    };

    request.open("GET", this.#url);
    request.send();
  }
複製程式碼

重新整理build-list.html並在瀏覽器控制檯中再次檢視 Scopesresponseundefined ,因為我們要訪問的 thisList。這與箭頭函式和類的行為一致(類預設為嚴格模式)。那麼現在有什麼解決辦法嗎?

第8章 DOM 和 events 中瞭解到,作為事件監聽器傳遞的每個回撥都可以訪問 event 物件。在該 event 物件中還有一個名為 target 的屬性,指向觸發事件的物件。吃準可以通過 event.target.response 獲取響應回來的資料。

 getData() {
    const request = new XMLHttpRequest();
    request.onload = event => {
      const response = JSON.parse(event.target.response);
      this.render(response);
    };

    request.open("GET", this.#url);
    request.send();
  }
複製程式碼

完整程式碼:

"use strict";

class List {
  #url;
  #target;

  constructor(url, target) {
    this.#url = url;
    this.#target = target;
    this.getData();
  }

  getData() {
    const request = new XMLHttpRequest();
    request.onload = event => {
      const response = JSON.parse(event.target.response);
      this.render(response);
    };

    request.open("GET", this.#url);
    request.send();
  }

  render(data) {
    const ul = document.createElement("ul");
    for (const element of data) {
      const li = document.createElement("li");
      const title = document.createTextNode(element.title);
      li.appendChild(title);
      ul.appendChild(li);
    }
    this.#target.appendChild(ul);
  }
}

const url = "https://academy.valentinog.com/api/link/";
const target = document.body;
const list = new List(url, target);    
複製程式碼

接著,繼續探索 XMLHttpRequest 的發展:Fetch

非同步演變:從 XMLHttpRequest 到 Fetch

Fetch API 是一種用於發出 AJAX 請求的原生瀏覽器方法,它常常被諸如 Axios 之類的庫所忽視。Fetch 與ES6 和新的 Promise 物件一起誕生於 2015 年。

另一方面,AJAX 從 1999 年開始就有了一套在瀏覽器中獲取資料的技術。現在我們認為 AJAX 和 Fetch 是理所當然的,但是很少有人知道 Fetch 只不過是 XMLHttpRequest 的 “美化版”。Fetch 比典型的 XMLHttpRequest 請求更簡潔,更重要的是基於 Promise。這裡有一個簡單的事例:

fetch("https://academy.valentinog.com/api/link/").then(function(response) {
  console.log(response);
});
複製程式碼

如果在瀏覽器中執行它,控制檯將列印一個響應物件。根據請求的內容型別,需要在返回資料時將其轉換為JSON

fetch("https://academy.valentinog.com/api/link/").then(function(response) {
  return response.json();
}); 
複製程式碼

與你可能認為的相反,僅僅呼叫並沒有返回實際的資料。由於response.json()也返回一個 Promise ,因此需要進一步才能獲得 JSON 有效負載:

fetch("https://academy.valentinog.com/api/link/")
  .then(function(response) {
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  });
複製程式碼

FetchXMLHttpRequest 更方便、更乾淨,但它有很多特性。例如,必須特別注意檢查響應中的錯誤。在下一節中,我們們將瞭解關於它的更多資訊,同時從頭重新構建 Fetch

從頭開始重新構建 Fetch API

為了更好的理解 Fetch 原理,我們們重寫 fetch 方法。首先,建立一個名為fetch.html的新檔案,內容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Building Fetch from scratch</title>
</head>
<body>

</body>
<script src="fetch.js"></script>
</html>
複製程式碼

然後在相同的資料夾中建立另一個名為 fetch.js 的檔案,內容如下:

"use strict";

window.fetch = null;
複製程式碼

在第一行中,我們們確保處於嚴格模式,在第二行中,“取消”原始的Fetch API。現在我們們可以開始構建自己的 Fetch API 了。fetch 的工作方式非常簡單。它接受一個 url 並針對它發出一個 GET 請求:

fetch("https://academy.valentinog.com/api/link/").then(function(response) {
  console.log(response);
});
複製程式碼

當帶有 then 的函式說明該函式是“可鏈”的,這意味著它返回一個 Promise。因此,在 fetch.js 中,我們們建立一個名為 fetch 的函式,它接受一個 url 並返回一個新的 Promise。建立 Promise,可以呼叫Promise 建構函式,並傳入一個回撥函式來解析和拒絕:

function fetch(url) {
  return new Promise(function(resolve, reject) {
    // do stuff
  });
}
複製程式碼

完善程式碼:

"use strict";

window.fetch = fetch;

function fetch(url) {
  return new Promise(function(resolve, reject) {
    resolve("Fake response!");
  });
}

fetch("https://academy.valentinog.com/api/link/").then(function(response) {
  console.log(response);
});
複製程式碼

在控制檯中得到“Fake response!” 。當然,這仍然是一個無用的 fetch ,因為沒有從 API 返回任何東西。讓我們們在 XMLHttpRequest 的幫助下實現真正的行為。我們們已經知道了 XMLHttpRequest 建立請求方式。接著,將XMLHttpRequest 封裝到我們們的 Promise

function fetch(url) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    request.open("GET", url);
    request.onload = function() {
      resolve(this.response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    request.send();
  });
}
複製程式碼

被拒絕的 Promisecatch 處理:

fetch("https://acdemy.valentinog.com/api/link/")
  .then(function(response) {
    console.log(response);
  })
  .catch(function(error) {
    console.log(error);
  });
複製程式碼

現在,如果 url 是錯誤的,會列印具體的錯誤資訊到控制檯。如果 url 正確,則列印請求到資料:

【JS 口袋書】第 10 章:使用非同步 JavaScript

上述實現方式還不夠完善。首先,我們們需要實現一個返回 JSON 的函式。實際的 Fetch API 生成一個響應,可以稍後將其轉換為 JSON、blob 或 文字,如下所示(對於本練習的範圍,我們只實現 JSON 函式)

fetch("https://academy.valentinog.com/api/link/")
  .then(function(response) {
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  })
複製程式碼

實現該功能應該很容易。 似乎 “response” 可能是一個單獨帶有 json() 函式的實體。 JS 原型系統非常適合構建程式碼(請參閱第5章)。 我們們建立一個名為 Response 的建構函式和一個繫結到其原型的方法(在fetch.js中):

function Response(response) {
  this.response = response;
}

Response.prototype.json = function() {
  return JSON.parse(this.response);
};
複製程式碼

就這樣,我們們我們可以在 Fetch 中使用 Response:

window.fetch = fetch;

function Response(response) {
  this.response = response;
}

Response.prototype.json = function() {
  return JSON.parse(this.response);
};

function fetch(url) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    request.open("GET", url);
    request.onload = function() {
      // 前面:
      // resolve(this.response);
      // 現在:
      const response = new Response(this.response);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    request.send();
  });
}

fetch("https://academy.valentinog.com/api/link/")
  .then(function(response) {
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  })
  .catch(function(error) {
    console.log(error);
  });
複製程式碼

上面的程式碼在瀏覽器的控制檯中列印一個物件陣列。現在我們們來處理誤差。Fetch 的真實版本比我們的 polyfill 複雜得多,但是實現相同的行為並不困難。Fetch 中的響應物件有一個屬性,一個名為**“ok”**的布林值。該布林值在請求成功時設定為 true,在請求失敗時設定為 false。開發人員可以通過引發錯誤來檢查布林值並更改 Promise處理程式。 這是使用實際 Fetch 檢查狀態的方法:

fetch("https://academy.valentinog.com/api/link/")
  .then(function(response) {
    if (!response.ok) {
      throw Error(response.statusText);
    }
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  })
  .catch(function(error) {
    console.log(error);
  });
複製程式碼

如你所見,還有一個 "statusText" 。 在 Response 物件中似乎容易實現 "ok""statusText"。 當伺服器響應成功,response.oktrue

  • 狀態碼等於或小於200
  • 狀態碼小於 300

重構 Response 方法,如下所示:


function Response(response) {
  this.response = response.response;
  this.ok = response.status >= 200 && response.status < 300;
  this.statusText = response.statusText;
}

複製程式碼

這裡不需要建立 "statusText",因為它已經從原始 XMLHttpRequest 響應物件返回了。這意味著在構造自定義響應時只需要傳遞整個響應

function fetch(url) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    request.open("GET", url);
    request.onload = function() {
      // 前面:
      // var response = new Response(this.response);
      // 現在: pass the entire response
      const response = new Response(this);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    request.send();
  });
}
複製程式碼

但是現在我們們的 polyfill 有問題。 它接受單個引數 "url",並且僅對其發出 GET 請求。修復這個問題應該很容易。首先,我們可以接受第二個名為requestInit的引數。然後根據該引數,我們可以構造一個新的請求物件:

  • 預設情況下,發出 GET 請求
  • 如果提供,則使用 requestInit 中的 bodymethodheaders

首先,建立一個帶有一些名為 bodymethodheaders 的屬性的新 Request 函式,如下所示:

function Request(requestInit) {
  this.method = requestInit.method || "GET";
  this.body = requestInit.body;
  this.headers = requestInit.headers;
}
複製程式碼

但在此之上,我們可以為設定請求頭新增一個最小的邏輯

function fetch(url, requestInit) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    const requestConfiguration = new Request(requestInit || {});
    request.open(requestConfiguration.method, url);
    request.onload = function() {
      const response = new Response(this);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    // 設定 headers
    for (const header in requestConfiguration.headers) {
      request.setRequestHeader(header, requestConfiguration.headers[header]);
    }
    // more soon
  });
}

複製程式碼

setRequestHeader 可以在 XMLHttpRequest 物件上直接使用。 headers 對於配置 AJAX 請求很重要。 大多數時候,你可能想在 headers 中設定 application/json 或身份驗證令牌。

  • 如果沒有 body,則為空請求
  • 帶有一些有效負載的請求是 body 提供的
function fetch(url, requestInit) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    const requestConfiguration = new Request(requestInit || {});
    request.open(requestConfiguration.method, url);
    request.onload = function() {
      const response = new Response(this);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    // Set headers on the request
    for (const header in requestConfiguration.headers) {
      request.setRequestHeader(header, requestConfiguration.headers\[header\]);
    }
    // If there's a body send it
    // If not send an empty GET request
    requestConfiguration.body && request.send(requestConfiguration.body);
    requestConfiguration.body || request.send();
  });
}

複製程式碼

下面是完整的程式碼:

"use strict";

window.fetch = fetch;

function Response(response) {
  this.response = response.response;
  this.ok = response.status >= 200 && response.status < 300;
  this.statusText = response.statusText;
}

Response.prototype.json = function() {
  return JSON.parse(this.response);
};

function Request(requestInit) {
  this.method = requestInit.method || "GET";
  this.body = requestInit.body;
  this.headers = requestInit.headers;
}

function fetch(url, requestInit) {
  return new Promise(function(resolve, reject) {
    const request = new XMLHttpRequest();
    const requestConfiguration = new Request(requestInit || {});
    request.open(requestConfiguration.method, url);
    request.onload = function() {
      const response = new Response(this);
      resolve(response);
    };
    request.onerror = function() {
      reject("Network error!");
    };
    for (const header in requestConfiguration.headers) {
      request.setRequestHeader(header, requestConfiguration.headers[header]);
    }
    requestConfiguration.body && request.send(requestConfiguration.body);
    requestConfiguration.body || request.send();
  });
}

const link = {
  title: "Building a Fetch Polyfill From Scratch",
  url: "https://www.valentinog.com/fetch-api/"
};

const requestInit = {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify(link)
};

fetch("https://academy.valentinog.com/api/link/create/", requestInit)
  .then(function(response) {
    if (!response.ok) {
      throw Error(response.statusText);
    }
    return response.json();
  })
  .then(function(json) {
    console.log(json);
  })
  .catch(function(error) {
    console.log(error);
  });

複製程式碼

真正的 Fetch API 實現要複雜得多,並且支援高階特性。我們只是觸及了表面。可以改進程式碼,例如,新增 headers 的邏輯可以獨立存在於方法上。

此外,還有很大的空間可以新增新特性:支援 PUTDELETE 以及更多以不同格式返回響應的函式。如果你想看到更復雜的獲取 API polyfill,請檢視來自 Github的 工程師的 whatwg-fetch。你會發現與我們們的 polyfill 有很多相似之處。

總結

AJAX 讓我們有機會構建流暢的、使用者友好的介面,從而改變了我們構建 web 的方式。經典頁面重新整理的日子已經一去不復返了。

現在,我們們可以構建優雅的 JS 應用程式並在後臺獲取所需的資料。XMLHttpRequest 是用於發出HTTP 請求的優秀的舊遺留的 API,今天仍在使用,但其形式有所不同: Fetch API

程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具Fundebug

原文:github.com/valentinoga…

交流

乾貨系列文章彙總如下,覺得不錯點個Star,歡迎 加群 互相學習。

github.com/qq449245884…

因為篇幅的限制,今天的分享只到這裡。如果大家想了解更多的內容的話,可以去掃一掃每篇文章最下面的二維碼,然後關注我們們的微信公眾號,瞭解更多的資訊和有價值的內容。

clipboard.png

每次整理文章,一般都到2點才睡覺,一週4次左右,挺苦的,還望支援,給點鼓勵

【JS 口袋書】第 10 章:使用非同步 JavaScript

相關文章