What is your worst nightmare?
你最可怕的噩夢是什么?
That sounded dark, but it’s not a rhetorical question. I really want to know because I am about to tell you mine. Along the way, we will learn some things like how the fetch API works and also how function constructors work.
聽起來很黑,但這不是一個反問。 我真的很想知道,因為我要告訴你我的。 在此過程中,我們將學習一些知識,例如獲取API的工作方式以及函數構造函數的工作方式。
Sorry I digress, back to my worst nightmare. If you had asked me that question last week it would be the below list in no particular order:
對不起,我離題了,回到我最糟糕的噩夢。 如果您上周曾問過我這個問題,則該列表按以下順序排列:
- Writing Pre-ES6 syntax 編寫ES6之前的語法
- No fetch API 沒有提取API
- No Transpiler (Babel/Typescript) 無轉換器(Babel /打字稿)
Uncle Bob said that I’m a disappointment (Kidding)
鮑伯叔叔說我很失望(開玩笑)
If your list matches mine then I have to say that you are a very weird person. As luck would have it I was called to work on a project that brought to life my nightmare list (excluding the last one). I was to add a new feature to the application. It was a legacy codebase that used purely pre-es6 syntax and XMLHttpRequest (the horror) for its AJAX requests.
如果您的名單與我的名單相符,那么我不得不說您是一個非常奇怪的人。 幸運的是,我被要求從事一個使我的噩夢清單(不包括最后一個清單)栩栩如生的項目。 我當時要向應用程序添加新功能。 它是一個遺留代碼庫,僅對其AJAX請求使用純es6之前的語法和XMLHttpRequest(恐怖)。
So in a bid to make the experience palatable, I decided to create a function that abstracts all the AJAX requests I would be making and expose APIs that mimics the new fetch API (well not really). This is also after I watched the Javascript: The new hard parts video on frontend masters where an amazing explanation of how the fetch API works under the hood was given. Let’s begin.
因此,為了使體驗變得可口,我決定創建一個函數,該函數抽象我將要發出的所有AJAX請求,并公開模仿新的訪存API的API (并非如此)。 這也是在我觀看了Javascript:前端大師上的新硬部分視頻之后,其中對fetch API的工作原理進行了令人驚訝的解釋。 讓我們開始。
First, I had to look up how XMLHttpRequest works. Then I started writing the function. My first iteration looked like this:
首先,我必須查看XMLHttpRequest的工作方式。 然后,我開始編寫函數。 我的第一次迭代看起來像這樣:
"use strict";function fetch() {var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};var xhr = new XMLHttpRequest();var onFufillment = [];var onError = [];var onCompletion = [];var method = "GET" || options.method;xhr.onreadystatechange = function () {var _data = this;if (this.readyState == 4 && this.status == 200) {// Action to be performed when the document is read;onFufillment.forEach(function (callback) {callback(_data);});onCompletion.forEach(function (callback) {callback(_data);});} else if (this.readyState == 4 && this.status !== 200) {onError.forEach(function (callback) {callback(_data);});onCompletion.forEach(function (callback) {callback(_data);});}};xhr.open(method, url, true);xhr.send();return {then: function then(fufillmentFunction) {onFufillment.push(fufillmentFunction);},catch: function _catch(errorFunction) {onError.push(errorFunction);},finally: function _finally(completionFunction) {onCompletion.push(completionFunction);}};
}
Let me work through what the function does:
讓我來研究一下函數的作用:
We are checking if the
url
argument is passed into the function. Defaulting to an empty string if nothing is passed我們正在檢查
url
參數是否傳遞給函數。 如果未傳遞任何內容,則默認為空字符串We are also doing the same thing for the
options
argument. Defaulting to an empty object if nothing is passed我們對
options
參數也做同樣的事情。 如果未傳遞任何內容,則默認為空對象- Then we create a new instance of the XMLHttpRequest 然后我們創建一個XMLHttpRequest的新實例
We create 4 variables
onFufillment, onError, onCompletion and method
我們創建4個變量
onFufillment, onError, onCompletion and method
onFufillment
is an array that stores all the functions passed into thethen
methodonFufillment
是一個數組,用于存儲傳遞給then
方法的所有函數onError
is an array that stores all the functions passed into thecatch
methodonError
是一個數組,用于存儲傳遞給catch
方法的所有函數onCompletion
is an array that stores all the functions passed into thefinally
methodonCompletion
是一個數組,用于存儲傳遞給finally
方法的所有函數method
is used to store the HTTP method that will be used, it defaults toGET
method
用于存儲將要使用的HTTP方法,默認為GET
We then pass a function into the
onreadystatechange
method ofxhr
which will be called when the state of the request changes然后,我們將一個函數傳遞到
xhr
的onreadystatechange
方法中,該方法將在請求狀態更改時被調用In the function, we save
this
into a_data
variable so that it can be passed into the forEach functions without losing its context (I knowthis
is annoying)在功能方面,我們保存
this
為_data
變量,以便它可以傳遞到在foreach功能沒有失去它的上下文(我知道this
很煩人)We then check if the request is completed (
readyState == 4
) and if the request is successful, then we loop throughonFufillment and onCompletion
arrays, calling each function and passing_data
into it然后,我們檢查,如果該請求已完成(
readyState == 4
)如果請求是成功的,那么我們循環onFufillment and onCompletion
數組,調用每個函數并傳遞_data
進去If the request fails we do the same thing with the
onCompletion and onError
arrays如果請求失敗,我們將使用
onCompletion and onError
數組執行相同的操作- Then we send off the request with the passed in parameters 然后,我們使用傳入的參數發送請求
After that, we return an object containing three functions, then.
catch and finally
which have the same names as the fetch API.之后,我們返回一個包含三個函數的對象。
catch and finally
與fetch API具有相同的名稱。catch
pushes the function that is passed as an argument into theonError
arraycatch
將作為參數傳遞的函數推入onError
數組then
does the same thing with theonFufillment
arraythen
用onFufillment
數組做同樣的事情finally
does the same with theonCompletion
arrayfinally
對onCompletion
數組做同樣的onCompletion
The usage of this API will look like this:
該API的用法如下所示:
var futureData = fetch('https://jsonplaceholder.typicode.com/todos/2');
futureData.then(function(data){console.log(data)
})futureData.finally(function(response){console.log(response);
});futureData.catch(function(error){console.log(error);
})
It works!!! But not nearly as the real fetch implementation. Can we do better than this? Of course, we can. We can still add more features to the function. We could make it chainable, that is, we can give it the ability to chain methods together.
有用!!! 但還不及真正的獲取實現。 我們可以做得更好嗎? 當然,我們可以。 我們仍然可以向該功能添加更多功能。 我們可以使其可鏈接,也就是說,我們可以使它將方法鏈接在一起。
On the second iteration, this is how it looks:
在第二次迭代中,它是這樣的:
"use strict";function fetch() {var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var xhr = new XMLHttpRequest();var onFufillment = [];var onError = [];var onCompletion = [];var method = "GET" || options.method;xhr.onreadystatechange = function () {var _data = this;if (this.readyState == 4 && this.status == 200) {// Action to be performed when the document is read;onFufillment.forEach(function (callback) {callback(_data);});onCompletion.forEach(function (callback) {callback(_data);});} else if (this.readyState == 4 && this.status !== 200) {onError.forEach(function (callback) {callback(_data);});onCompletion.forEach(function (callback) {callback(_data);});}};xhr.open(method, url, true);xhr.send();return {then: function then(fufillmentFunction) {onFufillment.push(fufillmentFunction);return this;},catch: function _catch(errorFunction) {onError.push(errorFunction);return this;},finally: function _finally(completionFunction) {onCompletion.push(completionFunction);return this;}};
}
The usage of the API will look like this:
API的用法如下所示:
var futureData = fetch('https://jsonplaceholder.typicode.com/todos/2');futureData.then(function(data){console.log(data)
}).then(function(response){console.log(response);
}).catch(function(error){console.log(error);
});
What did it do? The only difference in the second iteration was in the then, catch and finally
where I just returned this
which means each function returns itself basically enabling it to be chained (partially).
它做了什么? 在第二迭代中的唯一區別是在then, catch and finally
,我剛回到this
意味著每個函數返回本身基本上使它能夠被鏈接(部分地)。
Better right? But can we do better than this? Of course, we can. The returned object can be put in the function's prototype so that we can save memory in a situation where the function is used multiple times.
更好吧? 但是我們能做得更好嗎? 當然,我們可以。 可以將返回的對象放在函數的原型中,以便在多次使用函數的情況下節省內存。
This is how it looks on the third iteration:
這是在第三次迭代中的樣子:
"use strict";
function fetch() {var fetchMethod = Object.create(fetch.prototype);var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var xhr = new XMLHttpRequest();fetchMethod.onFufillment = [];fetchMethod.onError = [];fetchMethod.onCompletion = [];var method = "GET" || options.method;xhr.onreadystatechange = function () {var _data = this;if (this.readyState == 4 && this.status == 200) {// Action to be performed when the document is read;fetchMethod.onFufillment.forEach(function (callback) {callback(_data);});fetchMethod.onCompletion.forEach(function (callback) {callback(_data);});} else if (this.readyState == 4 && this.status !== 200) {fetchMethod.onError.forEach(function (callback) {callback(_data);});fetchMethod.onCompletion.forEach(function (callback) {callback(_data);});}};xhr.open(method, url, true);xhr.send();return fetchMethod;
};
fetch.prototype.then = function(fufillmentFunction) {this.onFufillment.push(fufillmentFunction);return this;
};
fetch.prototype.catch = function(errorFunction) {this.onError.push(errorFunction);return this;
};
fetch.prototype.finally = function(completionFunction) {this.onCompletion.push(completionFunction);return this;
};
So this version basically moves the returned function into the fetch’s prototype. If you don’t understand the statement then I recommend checking out this article about Javascript’s prototype (Thanks, Tyler McGinnis).
因此,此版本基本上將返回的函數移至提取的原型中。 如果您不理解該聲明,那么我建議您閱讀有關Javascript原型的本文(謝謝,Tyler McGinnis)。
Is this an improvement? Yes!!! Can we do better? Of course, we can. We can use the new
keyword to our advantage here and remove the explicit return statement.
這有改善嗎? 是!!! 我們可以做得更好嗎? 當然,我們可以。 我們可以在這里使用new
關鍵字來發揮優勢,并刪除顯式的return語句。
The next iteration will look like this:
下一次迭代將如下所示:
"use strict";
function Fetch() {var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};var xhr = new XMLHttpRequest();this.onFufillment = [];this.onError = [];this.onCompletion = [];var method = "GET" || options.method;var internalFetchContext = this;xhr.onreadystatechange = function () {var _data = this;if (this.readyState == 4 && this.status == 200) {// Action to be performed when the document is read;internalFetchContext.onFufillment.forEach(function (callback) {callback(_data);});internalFetchContext.onCompletion.forEach(function (callback) {callback(_data);});} else if (this.readyState == 4 && this.status !== 200) {internalFetchContext.onError.forEach(function (callback) {callback(_data);});internalFetchContext.onCompletion.forEach(function (callback) {callback(_data);});}};xhr.open(method, url, true);xhr.send();
};
Fetch.prototype.then = function(fufillmentFunction) {this.onFufillment.push(fufillmentFunction);return this;
};
Fetch.prototype.catch = function(errorFunction) {this.onError.push(errorFunction);return this;
};
Fetch.prototype.finally = function(completionFunction) {this.onCompletion.push(completionFunction);return this;
};
Let me explain the changes:
讓我解釋一下這些變化:
Changed the name of the function from fetch to Fetch, it’s just a convention when using the
new
keyword將函數的名稱從fetch更改為Fetch,這只是使用
new
關鍵字時的約定Since I am using the
new
keyword I can then save the various arrays created to thethis
context.由于我使用的是
new
關鍵字,因此可以將創建的各種數組保存this
上下文中。Because the function passed into
onreadystatechange
has its own context I had to save the originalthis
into its own variable to enable me to call it in the function (I know,this
can be annoying)由于傳遞給函數
onreadystatechange
都有自己的內容,我不得不原來保存this
到它自己的變量,使我能夠調用它的功能(我知道,this
可能是討厭)- Converted the prototype functions to the new function name. 將原型函數轉換為新的函數名稱。
The usage will look like this:
用法如下所示:
var futureData = new Fetch('https://jsonplaceholder.typicode.com/todos/1');
futureData.then(function(data){console.log(data)
}).then(function(response){console.log(response);
}).catch(function(error){console.log(error);
})
Voilà! That was really fun. But can we do better? Of course, we can.
瞧! 那真的很有趣。 但是,我們可以做得更好嗎? 當然,我們可以。
But I will leave that to you. I would love to see your own implementation of the API in the comments below.
但我會留給你。 我希望在下面的評論中看到您自己的API實現。
If you liked the article (and even if you didn’t), I would appreciate a clap (or 50) from you. Thank you.
如果您喜歡這篇文章(即使您不喜歡),也希望得到您的鼓掌(或50)。 謝謝。
翻譯自: https://www.freecodecamp.org/news/create-a-custom-fetch-api-from-xmlhttprequest-2cad1b84f07c/