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/