ในการเรียกใช้ Ajax หรือ XHR นั้นบางครั้งจำเป็นจะต้องเรียกหลาย URL ต่อครั้ง ยกตัวอย่างเช่นการอัพโหลดแบบ chunk เป็นต้น. การเรียก 1 ครั้ง ปกติแล้ว JavaScript จะไม่รอให้ว่าจะสำเร็จก่อนแล้วค่อยไปต่อหรืออย่างไร แต่มีเท่าไหร่มันก็สั่งเรียกให้หมดเลย ทำให้บางครั้งเกิดการเรียก URL เต็มจนท่วม server ส่งผลให้ firewall มองว่าเป็นการโจมตีแล้วทำการบล็อคไปเลย เช่นนี้เป็นต้น.
การจำกัดจำนวนการเรียกใช้ Ajax หลายๆรายการต่อครั้งจึงเป็นสิ่งจำเป็น. ต่อไปนี้เป็นโค้ดตัวอย่าง
/**
* Function สำหรับเรียก XHR อย่างง่ายๆ จะเปลี่ยนไปใช้ jQuery.ajax() ก็ได้เช่นกัน.
*/
function XHR(url, formData) {
return new Promise((resolve, reject) => {
let XHR = new XMLHttpRequest();
XHR.addEventListener('abort', (event) => {
reject({'response': '', 'status': (event.currentTarget ? event.currentTarget.status : ''), 'event': event});
});
XHR.addEventListener('error', (event) => {
reject({'response': '', 'status': (event.currentTarget ? event.currentTarget.status : ''), 'event': event});
});
XHR.addEventListener('timeout', (event) => {
reject({'response': '', 'status': (event.currentTarget ? event.currentTarget.status : ''), 'event': event});
});
XHR.addEventListener('load', (event) => {
let response = XHR.response;
let headers = XHR.getAllResponseHeaders();
let headerMap = {};
if (headers) {
let headersArray = headers.trim().split(/[\r\n]+/);
headersArray.forEach(function (line) {
let parts = line.split(': ');
let header = parts.shift();
let value = parts.join(': ');
headerMap[header] = value;
});
}
if (event.currentTarget && event.currentTarget.status >= 200 && event.currentTarget.status < 300) {
resolve({'response': response, 'status': event.currentTarget.status, 'event': event, 'headers': headerMap});
} else {
reject({'response': response, 'status': event.currentTarget.status, 'event': event, 'headers': headerMap});
}
});
XHR.open('POST', url);
XHR.responseType = 'json';
XHR.send(formData);
});// end new Promise
}// XHR
xhr.js
<?php
// server.php
sleep(1);
$output = [];
$output['round'] = (int) ($_GET['round'] ?? 0);
header('Content-Type: application/json');
echo json_encode($output);
server.php
<!--demo.html-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>XHR multi concurrent connection</title>
<style>
#debug {
border: 3px dashed #ccc;
margin: 10px 0;
padding: 10px;
}
</style>
</head>
<body>
<form id="upload-form" method="post">
<input type="hidden" name="hidden-name" value="hidden-value (unnecessary)">
<button type="submit">Submit</button>
<div id="debug"></div>
</form>
<script src="xhr.js"></script>
<script type="application/javascript">
let debugElement = document.getElementById('debug');
const thisForm = document.getElementById('upload-form');
const maxConcurrentConnection = 3;
thisForm.addEventListener('submit', (event) => {
event.preventDefault();
let loopPromise = Promise.resolve();
let ajaxCons = [];
for (let i = 0; i < 10; i++) {
loopPromise = loopPromise.then(() => {
if (ajaxCons.length < maxConcurrentConnection) {
debugElement.insertAdjacentHTML('beforeend', '<p>Calling to server round '+ i + '. (number from JavaScript.)</p>');
ajaxCons.push(
XHR('server.php?round=' + i)
.then((responseObject) => {
const response = responseObject.response;
debugElement.insertAdjacentHTML('beforeend', '<p> > Response from server: Round: <code>' + response.round + '</code></p>');
return Promise.resolve();
})
.catch((responseObject) => {
console.warn('response: ', responseObject);
debugElement.insertAdjacentHTML('beforeend', '<p style="color: red;"> > Error! see console.</p>');
return Promise.reject();
})
.finally(() => {
})
);
if ((parseInt(ajaxCons.length)) === parseInt(maxConcurrentConnection)) {
return new Promise((resolve, reject) => {
Promise.all(ajaxCons)
.then((value) => {
console.log('value', value);
ajaxCons = [];
resolve();
});
});
}
}
});
}
/*
// basic example loop make ajax call and wait until finish then next loop...
let loopPromise = Promise.resolve();
for (let i = 0; i < 10; i++) {
loopPromise = loopPromise.then(() => {
console.log('round ' + i);
return new Promise((resolve, reject) => {
XHR('server.php?round=' + i)
.then((responseObject) => {
const response = responseObject.response;
console.log('response: ', response);
resolve();
})
.catch((responseObject) => {
console.warn('response: ', responseObject);
reject();
});
});
});
}
*/
});
</script>
</body>
</html>
demo.html
จากโค้ดตัวอย่างด้านบน มันจะทำการจำลองเรียก URL หลายๆครั้งผ่าน for
loop. ทุกๆครั้งที่เรียก จะผลักการเรียกเข้าไปใน array ajaxCons
โดยเช็คไม่ให้เกิดจำนวน concurrent หรือการเชื่อมต่อพร้อมกันสูงสุดที่กำหนดไว้คือ 3. ถ้าหากเกินจำนวน ก็จะทำการรอผ่าน Promise
object.
เมื่อทุกๆ request ทำงานเสร็จแล้วด้วยการตรวจผ่าน Promise.all()
มันจะทำการ resolve เพื่อคลายการรอให้ loop รอบต่อไปทำงานได้อีก 3 การเชื่อมต่อ โดยจะทำซ้ำแบบนี้ไปเรื่อยๆจนกว่าจะครบ 10 รอบ.
ทั้งนี้หากผู้อ่านจะดัดแปลงให้เสร็จเฉพาะ request ใดๆไม่ต้องรอทั้งหมดก็ได้เช่นกัน โดยแก้ไขบรรทัดที่เป็นโค้ด ajaxCons = [];
มาเป็นajaxCons.splice(0, 1);
และเปลี่ยนจาก Promise.all()
มาเป็น Promise.any()
.
ดูตัวอย่างซอร์สโค้ดเต็มบน GitHub.