קריאה מודרכת בקוד של Axios
אקסיוס החלה כפרויקט צד של מתכנת בשם מאט זבריסקי. הוא בדיוק גילה את ריאקט ומאוד התלהב ואז באיזה ערב אחרי כנס JavaScript הוא נשאר במלון והתחיל לכתוב ספריית http לריאקט. מהר מאוד הוא הבין שהספריה שלו לא באמת קשורה רק לריאקט ו axios הפכה לספריית תקשורת גנרית לדפדפנים ול Node.JS.
היום מאט כבר כמה שנים עובד באפל ומי שמתחזק את Axios הוא מתכנת אחר בשם ג'ייסון סיימן. אקסיוס מקבל תרומות קוד ממאות מתכנתים בעולם ועומד על מעל 24 מיליון הורדות בשבוע ב npm.
בפוסט היום אסתכל על הארכיטקטורה ומבנה הפרויקט וגם אחשוב על רעיונות לתרומות קוד אפשריות.
1. מבנה התיקיות
כל הקוד של הפרויקט יושב בתיקיה בשם lib, אבל לפני שניכנס אליה מעניין לראות מה יש בכל התיקיות האחרות בריפו:
הספריה הראשונה שקופצת מול העיניים היא test. אני אוהב פרויקטים עם בדיקות כי הבדיקות הן צעד ביניים בין התיעוד לקוד עצמו. הן גם מספרות לא מעט על הקוד, וגם מאפשרות להריץ את הקוד בסביבה מבוקרת.
לאקסיוס, חוץ מתיקיית הבדיקות הרגילה test יש עוד שתי תיקיות בשם sandbox ו examples. מישהו מאוד התאמץ לעשות חיים קלים למתכנתים אחרים שרוצים להצטרף ולשחק עם הקוד - וזה עוד סימן טוב.
הספריה dist קצת מטרידה. אנחנו מסתכלים על ריפו של קוד מקור, ו dist מכילה תוצאה של בניה. למה צריך גם את dist שם? למה לא לתת לאנשים לבנות לבד? ומה אם אני אבנה והתוצאה לא תהיה זהה למה ששמור ב dist? למה לא לבנות עם Github Action ולהעלות את התוצאה ל Release בגיטהאב?
התיקיה הנסתרת
.github
כוללת קבצים לאינטרקציה עם גיטהאב. יש שם תבנית לפתיחת Issue ויותר מעניין הקובץ.github/workflows/ci.yml
שמכיל את ההוראות ל Github Action שמריץ את הבדיקות.
אחרי סקירת עץ התיקיות הצעד המתבקש הבא הוא לנסות להריץ את הבדיקות ולנסות לבנות את הפרויקט כדי לראות קודם כל שכל הבדיקות עוברות ושהבניה של גירסה נקיה מגיטהאב תייצר בדיוק את מה שכבר קיים בתיקיית dist.
אני מפעיל:
$ npm install
כדי להתקין את כל התלויות, ופותח את package.json כדי לראות איזה סקריפטים מחכים שם:
"scripts": {
"test": "grunt test && dtslint",
"start": "node ./sandbox/server.js",
"build": "NODE_ENV=production grunt build",
"preversion": "grunt version && npm test",
"version": "npm run build && git add -A dist && git add CHANGELOG.md bower.json package.json",
"postversion": "git push && git push --tags",
"examples": "node ./examples/server.js",
"coveralls": "cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"fix": "eslint --fix lib/**/*.js"
},
ושוב אני רואה שהפרויקט מאוד ידידותי למתכנתים חדשים. נכון הם משתמשים ב grunt אבל אולי נצליח להתחמק מקריאת ה Gruntfile. הרצת הבדיקות עם:
$ npm run test
עובדת ואני שם לב שמריצה שני סוגים של בדיקות - הבלוק הראשון הוא בלוק של בדיקות mocha שרצות בתוך node.js, והבלוק השני הוא בלוק של בדיקות karma שרצות בתוך דפדפנים. הוא אפילו כותב לי איזה דפדפנים הוא הריץ:
28 01 2022 10:34:29.443:INFO [karma-server]: Karma v6.3.11 server started at http://localhost:9876/
28 01 2022 10:34:29.444:INFO [launcher]: Launching browsers FirefoxHeadless, ChromeHeadless with concurrency unlimited
והנה הרגע שחששתי ממנו - בגלל שיש שני סוגי בדיקות ושני כלים שונים שמריצים אותן, ואני רוצה לדעת איזה בדיקות קשורות לאיזה כלי, אני נאלץ לפתוח את ה Gruntfile. הבלוק שמעניין אותנו ממנו הוא:
karma: {
options: {
configFile: 'karma.conf.js'
},
single: {
singleRun: true
},
continuous: {
singleRun: false
}
},
mochaTest: {
test: {
src: ['test/unit/**/*.js']
},
options: {
timeout: 30000
}
},
אוקיי אז מוקה מריץ את הבדיקות בתיקיית test/unit
וקארמה מריץ ... אה יש לו גם קובץ קונפיגורציה משלו. נמשיך לפתוח את karma.conf.js ושם נמצא את הבלוק:
preprocessors: {
'test/specs/__helpers.js': ['webpack', 'sourcemap'],
'test/specs/**/*.spec.js': ['webpack', 'sourcemap']
},
כלומר מוקה מריץ את הבדיקות בתיקיית test/unit
וקארמה מריץ את הבדיקות בתיקיית test/specs
. קארמה משתמש בדפדפנים ולכן בודק את החלק של הדפדפן בקוד של אקסיוס, ומוקה רץ בתוך node.js ולכן בודק את ההרצות מתוך יישום node.
לפני הכניסה לתיקיות הבדיקות ננסה לבנות את הפרויקט כדי לראות אם החשש שלי מ dist היה מוצדק. אני מפעיל:
$ npm run build
> axios@0.25.0 build
> NODE_ENV=production grunt build
Running "clean:dist" (clean) task
>> 5 paths cleaned.
Running "webpack" task
Hash: d004b5ae618258baef57
Version: webpack 4.46.0 / grunt-webpack 4.0.3
Time: 275ms
Built at: 01/28/2022 10:40:45 AM
Asset Size Chunks Chunk Names
axios.js 62.3 KiB main [emitted] main
axios.map 67.6 KiB main [emitted] [dev] main
Entrypoint main = axios.js axios.map
[./index.js] 40 bytes {main} [built]
[./lib/adapters/xhr.js] 6.79 KiB {main} [built]
[./lib/axios.js] 1.52 KiB {main} [built]
[./lib/cancel/Cancel.js] 385 bytes {main} [built]
[./lib/cancel/CancelToken.js] 2.41 KiB {main} [built]
[./lib/cancel/isCancel.js] 102 bytes {main} [built]
[./lib/core/Axios.js] 4.14 KiB {main} [built]
[./lib/core/InterceptorManager.js] 1.33 KiB {main} [built]
[./lib/core/mergeConfig.js] 3.12 KiB {main} [built]
[./lib/defaults.js] 3.5 KiB {main} [built]
[./lib/env/data.js] 43 bytes {main} [built]
[./lib/helpers/bind.js] 256 bytes {main} [built]
[./lib/helpers/isAxiosError.js] 373 bytes {main} [built]
[./lib/helpers/spread.js] 564 bytes {main} [built]
[./lib/utils.js] 8.65 KiB {main} [built]
+ 14 hidden modules
Hash: 298941b4c4e5b7c2fd49
Version: webpack 4.46.0 / grunt-webpack 4.0.3
Time: 196ms
Built at: 01/28/2022 10:40:45 AM
Asset Size Chunks Chunk Names
axios.min.js 17.3 KiB 0 [emitted] main
axios.min.map 79.3 KiB 0 [emitted] [dev] main
Entrypoint main = axios.min.js axios.min.map
[0] ./lib/utils.js 8.65 KiB {0} [built]
[1] ./lib/defaults.js 3.5 KiB {0} [built]
[2] ./lib/cancel/Cancel.js 385 bytes {0} [built]
[3] ./lib/helpers/bind.js 256 bytes {0} [built]
[4] ./lib/helpers/buildURL.js 1.61 KiB {0} [built]
[5] ./lib/core/enhanceError.js 1.11 KiB {0} [built]
[8] ./lib/cancel/isCancel.js 102 bytes {0} [built]
[9] ./lib/core/mergeConfig.js 3.12 KiB {0} [built]
[10] ./lib/env/data.js 43 bytes {0} [built]
[11] ./index.js 40 bytes {0} [built]
[12] ./lib/axios.js 1.52 KiB {0} [built]
[13] ./lib/core/Axios.js 4.14 KiB {0} [built]
[26] ./lib/cancel/CancelToken.js 2.41 KiB {0} [built]
[27] ./lib/helpers/spread.js 564 bytes {0} [built]
[28] ./lib/helpers/isAxiosError.js 373 bytes {0} [built]
+ 14 hidden modules
Done.
עושה רושם שזה הצליח. ועכשיו נשווה עם מה ששמור בגיט:
~/tmp/axios ╱ master !5 PAGER=cat git diff --name-only -- dist
dist/axios.js
dist/axios.map
dist/axios.min.js
dist/axios.min.map
ואנחנו מגיעים לבעיה הראשונה עם מבנה התיקיות: אם שמים בריפו תיקיית dist היא חייבת להתאים למה שאני אקבל מבניית הפרויקט.
2. בדיקות מוקה
נמשיך לתיקיות הבדיקות ותיקיית הבדיקות הראשונה שאני רוצה לקרוא היא התיקיה test/unit
. אני כבר יודע שאלה בדיקות שרצות בתוך node.js בלבד באמצעות מוקה. הקובץ המרכזי כאן הוא unit/adapters/http.js
, קובץ שכולל מעל אלף שורות של בדיקות. הנה בדיקה אחת לדוגמה ממנו:
it('should allow passing JSON', function (done) {
var data = {
firstName: 'Fred',
lastName: 'Flintstone',
emailAddr: 'fred@example.com'
};
server = http.createServer(function (req, res) {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(data));
}).listen(4444, function () {
axios.get('http://localhost:4444/').then(function (res) {
assert.deepEqual(res.data, data);
done();
});
});
});
אני מאוד אהבתי את סגנון הבדיקה המדויק - יוצרים אוביקט, פותחים שרת שיחזיר אותו עם Content-Type של application/json, ואז בודקים שכשנפנה לאותו שרת באמת נקבל את האוביקט מפוענח. בגלל שהבדיקות האלה רצות בצד שרת מאוד קל להרים שרת כדי לבדוק את כל התקשורת. בצד הדפדפן הם יצטרכו לעבוד קצת יותר קשה בשביל לקבל תוצאה דומה.
3. בדיקות קארמה
תיקיית הבדיקות השניה, specs
, היא גם הגדולה יותר וכנראה שכאן הושקעו עיקר המאמצים בכתיבת הבדיקות. יש בדיקות מכל הסוגים לדוגמה הקובץ test/specs/core/buildFullPath.spec.js
בודק פונקציה טהורה אחת בשם buildFullPath
:
var buildFullPath = require('../../../lib/core/buildFullPath');
describe('helpers::buildFullPath', function () {
it('should combine URLs when the requestedURL is relative', function () {
expect(buildFullPath('https://api.github.com', '/users')).toBe('https://api.github.com/users');
});
it('should return the requestedURL when it is absolute', function () {
expect(buildFullPath('https://api.github.com', 'https://api.example.com/users')).toBe('https://api.example.com/users');
});
it('should not combine URLs when the baseURL is not configured', function () {
expect(buildFullPath(undefined, '/users')).toBe('/users');
});
it('should combine URLs when the baseURL and requestedURL are relative', function () {
expect(buildFullPath('/api', '/users')).toBe('/api/users');
});
});
כשמגיעים לבדיקות התקשורת עצמה הבדיקה משתמשת ב Mocks כדי לדמות את התקשורת ולוודא שהקוד שולח בקשות ומפענח תשובות כמו שצריך. לדוגמה הבדיקה הבאה מתוך הקובץ test/specs/instance.spec.js
:
beforeEach(function () {
jasmine.Ajax.install();
});
afterEach(function () {
jasmine.Ajax.uninstall();
});
it('should make an http request with url instead of baseURL', function (done) {
var instance = axios.create({
url: 'https://api.example.com'
});
instance('/foo');
getAjaxRequest().then(function (request) {
expect(request.url).toBe('/foo');
done();
});
});
הבדיקה משתמשת במנגנון הריגול של jasmine כדי לתפוס בקשות Ajax ולהחליף אותן ב Mocks שאפשר יהיה לתשאל.
עכשיו גם יהיה זמן טוב להציץ לתיקיית lib
כדי לראות את מבנה תיקיות קבצי המקור, ונוכל לשים לב שמבנה תיקיית הבדיקה דומה מאוד למבנה תיקיית המקור. התיקיות core, cancel ו helpers נמצאות שלושתן גם בתיקיית המקור וגם בתיקיית הבדיקות. גם מבחינת הקבצים יש דמיון, לדוגמה הקובץ test/specs/defaults.spec.js
כנראה בודק את הקוד שנמצא בקובץ lib/defaults.js
. ההתאמה אינה אחד לאחד ויש הרבה קבצי בדיקות שאין להן קובץ מקור מתאים, וגם להיפך הרבה קבצי מקור בלי קובץ בדיקות מתאים.
היכולת לשנות קצת קוד ואז להריץ את הבדיקות כדי לראות שלא שברנו כלום היא קריטית בפרויקט קוד פתוח שרוצה לקבל תרומות ממתכנתים בכל העולם. כשאני מסתכל על פרויקט כמו axios הבדיקות עוזרות לי להרגיש בטוח לפני שאני מוסיף פיצ'ר ומייצר Pull Request, כי הן מורידות את הסיכוי שאשבור משהו בלי לשים לב.
4. איך אקסיוס מוציא בקשות http
אם הבדיקות מחולקות לקוד Node.JS וקוד דפדפן - כנראה שגם הקוד עצמו של אקסיוס בנוי כך. ואחרי שאנחנו יודעים להריץ את הבדיקות ולבנות את הפרויקט אנחנו במקום טוב להיכנס לתיקיית lib עצמה כדי לענות על השאלה הכי מעניינת לגבי אקסיוס - איך הוא שולח בקשות?
כשאקסיוס רק נולד עדיין לא היה fetch API בדפדפנים, וכשרצינו להוציא בקשות רשת היה צריך להכיר תחביר קצת מסורבל של XMLHttpRequest - היית צריך ליצור אחד, להגיד לו לאן ללכת ואיזה פונקציה הוא צריך להפעיל כשיסיים ולשלוח אותו לדרכו. רוב המתכנתים השתמשו ב jQuery כדי לעטוף את XMLHttpRequest בתחביר יותר ידידותי, ובעצם כשמאט זברינסקי נכנס לסצינה הוא מבין שאם אתה משתמש בריאקט אז אתה לא צריך jQuery, ואנשים השאירו את jQuery רק בשביל אותה עטיפה קטנה ל XMLHttpRequest. בעצם ב jQuery יכולת לכתוב:
$.get('/some/url', function(data) {
console.log(data);
});
וזה פשוט עבד ופנה ל /some/url
, הביא ממנו מידע בדרך כלל בצורת JSON ואז המשיך לבצע את הפונקציה בפרמטר השני עם המידע שקיבל.
נתחיל את המסלול שלנו באקסיוס בקובץ הראשי של הספריה lib/axios.js
ושם אני מוצא את השורה:
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
חוץ ממנה אין הרבה לוגיקה בקובץ, ולכן התחנה הבאה שלי היא הקובץ code/Axios
שם מוגדרת המחלקה Axios. זה כבר קובץ הרבה יותר מעניין שכמעט מגיע ל 150 שורות. הפונקציה המרכזית בו נקראת request. היא קצת ארוכה אז אני מדביק כאן רק את החלק שלה שקשור לשליחת בקשה:
var chain = [dispatchRequest, undefined];
Array.prototype.unshift.apply(chain, requestInterceptorChain);
chain = chain.concat(responseInterceptorChain);
promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
אז יש פה שלוש שורות ראשונות קצת מבלבלות כשהם מכינים מערך בשם chain, אבל מה שחשוב הוא הפונקציה dispatchRequest
שאחראית על שליחת הבקשה בפועל. הבעיה שההגדרה שלה עדיין לא כאן. אני ממשיך לקובץ dispatchRequest.js
באותה תיקיה וגם שם מוצא פונקציה ראשית קצת ארוכה עם הקוד הראשי הבא:
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// Transform response data
response.data = transformData.call(
config,
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
אוקיי אז בשביל לשלוח בקשה אנחנו פונים לפונקציה בשם adapter, מעבירים את פרטי הבקשה ומקביל בחזרה אוביקט response שימשיך לעבור טרנספורמציות לפני שיוחזר לקוד שקרא לאקסיוס. הטרנספורמציות הן כנראה פיענוח ה JSON ודברים נוספים בסגנון, אבל מי זה ה adapter?
בגלל שהוא מוגדר בתוך config ולא מיובא מבחוץ אני קצת תקוע. לכן אני מפעיל חיפוש על כל הקבצים בתיקיה:
~/tmp/axios/lib ╱ master !5 ack -l adapter
core/README.md
core/dispatchRequest.js
core/mergeConfig.js
adapters/README.md
defaults.js
ואחרי חיטוט מהיר בכולם מגיע ל defaults.js ושם לפונקציה:
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// For node use HTTP adapter
adapter = require('./adapters/http');
}
return adapter;
}
אוקיי עכשיו אני מבין! אז הם בעצם מסתכלים אם אני בדפדפן (כלומר אם יש לי XMLHttpRequest) כדי להשתמש ב adapter בשם xhr, שכנראה עוטף את XMLHttpRequest, או אם אני ב node ואז ילכו על adapter בשם http, שכנראה נותן עטיפה דומה למנגנון התקשורת המובנה של node.
5. תיקיית adapters
כבר ראינו שבתיקיית test/unit/adapters
יש קובץ בשם http.js
שבודק את קוד התקשורת, ועכשיו אנחנו רואים שיש לו גם מקבילה בתיקיית המקור: הקובץ lib/adapters/http.js
. קובץ זה עוטף את מנגנון התקשורת הפנימי של node למבנה של Axios Adapter.
החבר השני בתיקיה הוא הקובץ xhr.js. לקובץ זה אין קובץ בדיקות, וזה הגיוני. אם אני עושה Mocking לכל קוד התקשורת בדפדפן, אז אני לא באמת יכול לבדוק את הקוד שאחראי על התקשורת. זה באמת היה נכון בעבר, אבל היום כבר אפשר להשתמש בספריה כמו Mock Service Worker כדי לבנות מיני שרת ווב בתוך הדפדפן ולבדוק חלק גדול מקוד התקשורת.
מבחינת הקוד בקובץ lib/adapters/xhr.js
אין הפתעות גדולות. אנחנו מוצאים בתחילת הפונקציה את:
var request = new XMLHttpRequest();
ובהמשך הגדרת כל המאפיינים של אותה request עד הסיום עם:
request.send(requestData);
מה שכן כדאי לשים לב בבחירה לבנות שני קבצים שונים עבור התקשורת הוא כפל הקוד שיש בין שני הקבצים. הקטע הבא מטפל בביטול בקשה בקובץ xhr.js
:
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = function(cancel) {
if (!request) {
return;
}
reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
והקטע הזה מטפל בביטול בקשה בקובץ ה adapter השני, הקובץ adapters/http.js
:
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = function(cancel) {
if (req.aborted) return;
req.abort();
reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
ארגון מחדש של הקוד בשני ה adapters היה יכול לחסוך חלק גדול מהכפילות, ואולי לעשות חיים יותר קלים למי שיבוא לבנות adapter חדש.
6. ארגז החול ותיקיית הדוגמאות
שתי התיקיות הבאות הן כבר לא סטנדרטיות בפרויקטי קוד פתוח. התיקייה examples כוללת קובץ בשם server.js, שאם תריצו אותו תקבלו שרת שיודע להגיש 6 דוגמאות שונות (מסודרות יפה בתוך תיקיות בתוך examples), כל דוגמה מספרת משהו על איך להשתמש ב axios. לדוגמה הקובץ examples/get/index.html
מראה לנו איך להשתמש ב axios כדי לקבל ב Ajax רשימת פריטים מהשרת ולשתול אותה בתוך קובץ html:
<!doctype html>
<html>
<head>
<title>axios - get example</title>
<link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"/>
</head>
<body class="container">
<h1>axios.get</h1>
<ul id="people" class="list-unstyled"></ul>
<script src="/axios.min.js"></script>
<script>
axios.get('/get/server')
.then(function (response) {
document.getElementById('people').innerHTML = response.data.map(function (person) {
return (
'<li class="row">' +
'<img src="https://avatars.githubusercontent.com/u/' + person.avatar + '?s=50" class="col-md-1"/>' +
'<div class="col-md-3">' +
'<strong>' + person.name + '</strong>' +
'<div>Github: <a href="https://github.com/' + person.github + '" target="_blank">' + person.github + '</a></div>' +
'<div>Twitter: <a href="https://twitter.com/' + person.twitter + '" target="_blank">' + person.twitter + '</a></div>' +
'</div>' +
'</li><br/>'
);
}).join('');
})
.catch(function (err) {
document.getElementById('people').innerHTML = '<li class="text-danger">' + err.message + '</li>';
});
</script>
</body>
</html>
השימוש העיקרי בתיקיית הדוגמאות לדעתי היה אמור להיות לאפשר לאנשים לפתוח את הקוד מתוך אתר עריכת קוד אונליין כמו gitpod (ככה לפחות הם מספרים בתיעוד) ואז בלחיצה אחת אפשר לראות את axios בפעולה בתוך קובץ HTML עם קוד צד שרת לידה. במציאות בשביל לגשת לתיקיית הדוגמאות בגיטפוד צריך להיכנס לקישור גיטפוד שלהם כאן: https://gitpod.io/#https://github.com/axios/axios/blob/master/examples/server.js
לחכות שהפרויקט ייבנה ואז ללחוץ על הכפתור של חץ-קטן-בתוך-ריבוע כדי לפתוח את התוצאה בחלון חדש, אחרת קובץ ה server.js שלהם מחזיר שגיאת 404.
בתיקיית sandbox אנחנו יכולים למצוא קובץ HTML ושרת שמגיש אותו שמאפשר הרצה של axios מתוך טופס.
לדעתי שתי התיקיות examples ו sandbox לא מועילות במיוחד והיה אפשר לוותר עליהן.
7. רעיונות לתרומות קוד
אם הגעתם עד כאן בקריאה אתם כבר מבינים דבר או שניים על איך עובדת הספריה axios. אני רוצה לסיים את הסקירה בכמה הצעות שאתם יכולים לממש בשביל ללמוד עוד על הספריה ובסוף גם לשפר אותה:
- כרגע יש לאקסיוס adapters עבור XMLHttpRequest ועבור Node.JS. דפדפנים כבר כוללים API חדש שנקרא Fetch API שמאפשר עוד כמה יכולות בנוסף ל xhr. אפשר להוסיף Adapter עבור Fetch API.
תוספת כזאת יכולה לתת עוד יכולות לספריה, למשל היום הקוד של xhr לא תומך בהגבלת מספר ה Redirects כמו שמופיע ב Issue הזה: https://github.com/axios/axios/issues/1227.
מעבר ל Fetch יאפשר להפעיל את הפרמטר maxRedirects גם בדפדפן.
כרגע אין בדיקות ל adapter של xhr. הספריה MSW מאפשרת לבנות שרת http קטן מתוך הדפדפן. באמצעותה אפשר לבנות בדיקות לקוד התקשורת שירוצו גם בדפדפן וגם ב Node.JS, ויבדקו את שני ה adapters במכה אחת.
כרגע כל adapter מכיל את קוד התקשורת המלא שהוא צריך, מה שגורם לכפל קוד ביניהם לדוגמה בטיפול בביטול בקשה. אפשר לארגן אחרת את הקוד כדי לבטל את כפל הקוד הזה.
עד לפה סקירת הארכיטקטורה המרכזית של axios. ברור שיש עוד הרבה קוד לחזור אליו בספריה הזו, ואני מקווה שסקירה זו נתנה לכם את הכלים להתמצא טוב יותר בספריה ולהמשיך לצלול למנגנון שלה שמעניינים אתכם.