AJAX พร้อม Vanilla Node.js / JavaScript - โพสต์ JSON โดยไม่มีฟอร์ม
ฉันยังใหม่กับ node.js, ajax และรหัสอะซิงโครนัส ฉันได้รวบรวมวิธีการทำงานในการแลกเปลี่ยน json ระหว่างเบราว์เซอร์และเซิร์ฟเวอร์แล้วและฉันสงสัยว่ามีคนที่มีประสบการณ์มากกว่าคิดว่าวิธีนี้เหมาะสมกับความต้องการของฉันหรือไม่
บริบทความต้องการ: เทคนิคนี้จะใช้สำหรับการอัปเดตข้อมูลหลังสำนักงาน เริ่มแรกจะมีผู้ใช้หนึ่งคนบนคอมพิวเตอร์เครื่องเดียวกับฐานข้อมูล ฉันไม่ต้องการออกกฎอนุญาตให้ผู้ใช้รายอื่นอัปเดตแบ็กเอนด์จากระยะไกลในอนาคต แต่หากเป็นเช่นนั้นพวกเขาจะใช้หน้าจอที่เรียบง่ายกว่าที่ออกแบบโดยคำนึงถึงตำแหน่งระยะไกลเป็นหลัก เนื่องจากนี่เป็นแบ็คออฟฟิศฉันจึงสามารถยืนยันกับเบราว์เซอร์ Chrome รุ่นล่าสุดได้
ข้อ จำกัด ของฉัน: ฉันเป็นร้านขายของคนเดียวที่พัฒนา / สนับสนุนเว็บไซต์ในช่วงกึ่งเกษียณอายุ ฉันเป็นงานพาร์ทไทม์และความรับผิดชอบของฉันมีมากกว่าการเขียนโค้ดดังนั้นอย่างที่ดีที่สุดฉันสามารถเป็นแจ็คของการค้าบางอย่างได้
คุณสามารถข้ามไปที่“ ข้อกำหนด” ด้านล่างได้โดยตรงหรือหากต้องการบริบทเพิ่มเติมโปรดอ่าน:
ทำไมไม่ใช้ห้องสมุดเพื่อทำสิ่งนี้: ฉันยินดีที่จะได้รับคำสั่งว่าควร แต่ชอบที่จะหลีกเลี่ยงห้องสมุดเว้นแต่จะมีเหตุผลที่น่าสนใจให้ใช้ ฉันเป็นคนขายของ 1 คนที่ต้องหาวิธีทำบางอย่างจากนั้นก็ทำแบบอัตโนมัติซ้ำแล้วซ้ำเล่า มันเป็นสถานการณ์ที่แตกต่างจาก บริษัท ขนาดใหญ่ที่ทำสิ่งต่างๆมากมาย ตัวอย่างเช่นส่วนหน้าของแอปพลิเคชันนี้มีหน้าเว็บคงที่ครึ่งล้านหน้าซึ่งเขียนโดยส่วนหลัง ประเภทหน้าเว็บมีเพียงครึ่งโหลและแต่ละครั้งจะโหลดด้วยการกดครั้งเดียวไปยังเซิร์ฟเวอร์ พวกเขาเขียนด้วยวานิลลา html, css และ javascript ธรรมดา เมนูจะดูดีกว่าถ้ามีการใช้ไลบรารี (หรือเมื่อฉันใช้เวลากับมันมากขึ้น) แต่ก็มีคุณค่าในความเรียบง่าย ไลบรารีทำให้การควบคุมเวอร์ชันมีความซับซ้อนโดยทั่วไปจะมีความสามารถมหาศาลที่ฉันจะไม่ใช้และลากไปตามการสนับสนุนแบบเดิมที่ฉันไม่ต้องการ ประสบการณ์ส่วนตัวของฉันเทียบได้กับคนอื่น ๆ ฉันดีกว่าที่จะลงลึกในผลิตภัณฑ์เดียวมากกว่าที่ฉันจำได้ว่าอินเทอร์เฟซผลิตภัณฑ์หลายอย่าง
เหตุผลของเครื่องมือ: ไม่มีแผนสำหรับไซต์ในการสร้างรายได้ดังนั้นหนึ่งในเหตุผลที่ฉันเลือก MySQL และ node.js คือไม่มีค่าใช้จ่ายในการออกใบอนุญาตสำหรับงานอดิเรกที่ฉันหวังว่าจะดำเนินต่อไปเป็นเวลา 20 ปี เครื่องมือดูเหมือนจะตรงตามความต้องการของฉันและเป็นที่นิยมมากพอที่ฉันจะค้นหาวิธีการทำสิ่งต่างๆบนอินเทอร์เน็ตได้ นอกจากนี้ในฐานะที่เป็นร้านค้า 1 คนแบบพาร์ทไทม์การสามารถใช้ภาษาเดียวกันบนเซิร์ฟเวอร์และเบราว์เซอร์เป็นข้อได้เปรียบอย่างมาก
ความต้องการ:ด้วย vanilla node.js ธรรมดาให้ย้ายไฟล์ json ขนาดใหญ่ระหว่างเบราว์เซอร์และเซิร์ฟเวอร์เพื่อรองรับการดูแลข้อมูลแบ็กเอนด์สำหรับเว็บไซต์ ฉันได้ทดสอบโซลูชันปัจจุบันแล้วและมันย้ายข้อมูลมากกว่าที่ฉันต้องการเพื่อย้ายเร็วกว่าที่ฉันต้องการย้าย (ฉันทดสอบวัตถุ 100,000 ชิ้นที่ต้องการไฟล์ json 8Meg ทั้งสองวิธี) แต่ฉันไม่มีประสบการณ์ ajax และกลัวว่าอาจมีปัญหาที่ฉันไม่ได้คาดการณ์หรือวิธีง่ายๆในการทำ ajax ประเภทนี้ด้วยวิธีที่ดีกว่า
ประเด็นที่ฉันพิจารณา:
- CORS กำหนดให้เซิร์ฟเวอร์ที่ตอบสนองต่อคำร้องขอ ajax จะต้องให้บริการไฟล์ html ที่ร้องขอก่อน
- Ajax POST ไม่ได้ระบุไว้
- เบราว์เซอร์จะเรียกหาไอคอน Fav โดยอัตโนมัติ
บนเซิร์ฟเวอร์คำสั่ง switch จะใช้เวลานานเกินไปหากฉันยังคงเพิ่มกรณีสำหรับแต่ละหน้าการบำรุงรักษา ฉันจะทำความสะอาดและยินดีรับคำแนะนำทั่วไปที่คุณยินดีเสนอ แต่คำถามของฉันที่นี่คือถ้าเทคนิค ajax เหมาะสมกับความต้องการของฉันหรือไม่?
ฉันรวมภาพ colorized-VSCode ของรหัสเพื่อให้อ่านง่ายขึ้นรวมถึงรหัสจริงที่คุณสามารถคัดลอกไปยังคอมพิวเตอร์และเรียกใช้ โค้ดตัวอย่างสามารถทดสอบได้โดยใส่ไฟล์. js และ. html ในไดเร็กทอรีเดียวกันและตั้งชื่อเป็น "test_ajax_post_json_sans_form" คุณจะทดสอบโดยเรียกใช้ "node filename" ในคอนโซลคำสั่งจากนั้นโหลด http: // localhost : 8000 / filename ในเบราว์เซอร์ของคุณฉันใช้ Chrome
html พร้อมอิมเมจรหัสไคลเอนต์ ORIGINAL (ดูตัวอย่างข้อมูลสำหรับการอัปเดต):
ภาพ js:
ฉันรวมโค้ดไว้ในตัวอย่าง แต่ต้องใช้ node.js ในการทำงานดังนั้นคุณต้องคัดลอกไปยังคอมพิวเตอร์ที่มี node.js (HTML ปรับปรุงต่อคำแนะนำ 10/11):
'use strict';
const host = 'localhost';
const http = require('http'); // VSCode shows 3 dots under "require" and says something about NodeRequire???
const fs = require('fs').promises;
const port = 8000;
const requestListener = function (req, res) {
switch (req.url) {
case "/test_ajax_post_json_sans_form.html": // serve an intial html file
fs.readFile(__dirname + '/test_ajax_post_json_sans_form.html')
.then(contents => {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(contents);
})
.catch(err => {
res.writeHead(500);
res.end(err);
return;
});
break
case '/test_ajax_post_json_sans_form.html/ajaxTest1': // receive json, process, then return other json
let body = '';
req.on('data', (data) => {
body += data;
});
req.on('end', () => {
console.log(body); // to show all data has arrived
// here we will check or errors, create a complex return-object, stringify it, and send it back
const objToReturn = {data1: 'Test message from server', data2: 'could be a complex json object'};
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(objToReturn));
});
break
case '/favicon.ico':
// browser will call favicon automatically. This satisfies the request (though its failing
// won't keep the ajax from working).
fs.readFile(__dirname + '/favicon.ico')
.then(contents => {
res.writeHead(200, {'Content-Type': 'image/x-icon'});
res.end(contents);
})
.catch(err => { // lacking favicon will not impact test
res.writeHead(200, {'Content-Type': 'image/x-icon'});
res.end();
});
break
default:
res.writeHead(404);
res.end(JSON.stringify({error:'Resource not found'})); // in production, might load a not-found page here
}
}
const server = http.createServer(requestListener);
server.listen(port,host, () => { // binds the server object to a newtwork address
console.log(`Server is running on http://${host}:${port}. (^c to cancel)`);
});
<!DOCTYPE html><body>
<button type="button" id="submitButton">Send and then Receive JSON</button>
<br>After clicking the button, the JSON string sent from the browser to the server will show in the console.
<br>Then the JSON response string from the server will replace what is below.
<br><br>
<div id='messageArea'>
Message to replace via ajax.
</div>
<script>
'use strict';
const submitButton = document.getElementById('submitButton');
const messageArea = document.getElementById('messageArea');
async function exchangeJSON() {
try {
submitButton.disabled = true;
// here can build a complex object to send
const objToSend = {message1: 'Test message from browser', message2: 'could be a complex JSON string'};
const response = await fetch('test_ajax_post_json_sans_form.html/ajaxTest1', {
method: 'POST',
body: JSON.stringify(objToSend)
});
if (response.ok) {
const jsonResponse = await response.json();
// here can parse, update screen, etc.
messageArea.textContent = JSON.stringify(jsonResponse);
}
//throw new Error('Test error in ExchangeJSON'); // uncomment for testing
} catch (error) {
messageArea.textContent = error;
} finally {
submitButton.disabled = false;
}
}
submitButton.addEventListener('click',exchangeJSON);
</script>
</body></html>
คำตอบ
พิจารณาfetchในเบราว์เซอร์ที่ทันสมัยซึ่งรับประกันว่าโค้ดของคุณจะทำงานfetchอยู่มักเป็นทางเลือกที่ดีกว่าXMLHttpRequest- fetchเป็นแบบ Promise (สัญญามักจะทำงานได้ดีกว่าการเรียกกลับเล็กน้อย) API ของมันจะอ่านและเขียนได้สะอาดกว่าเล็กน้อย และกระชับขึ้นอีกเล็กน้อย
การจัดการข้อผิดพลาดส่วนหน้าไม่มีการจัดการข้อผิดพลาด หากคำขอล้มเหลวไม่ว่าด้วยเหตุผลใดก็ตามจะไม่มีการแจ้งให้ผู้ใช้ทราบ - หลังจากกดปุ่มแล้วดูเหมือนว่าจะมีการประมวลผลตลอดไปโดยไม่ยกเลิกการปิดใช้งานอีก พิจารณา
- แสดงข้อความแสดงข้อผิดพลาดหากมีข้อผิดพลาดและ
- เปิดใช้งานปุ่มอีกครั้งหากมีข้อผิดพลาด
การตอบกลับหากคุณต้องการแสดง JSON ของการตอบสนองต่อผู้ใช้:
- อย่าตั้งค่าการตอบสนองเป็น
innerHTMLองค์ประกอบ ซึ่งอาจส่งผลให้มีการใช้รหัสโดยอำเภอใจองค์ประกอบ HTML เพิ่มเติมที่ไม่คาดคิดและสิ่งแปลก ๆ ที่เกี่ยวข้องกับเอนทิตี HTML ใช้.textContentแทน - เนื่องจากการตอบสนองเป็น JSON อาจใช้องค์ประกอบที่คล้ายรหัสมากขึ้นเช่น a
<pre>?
การโทรกลับเมื่อคุณเพิ่มผู้ฟังหากคุณต้องการให้การโทรกลับทำงานหากการเรียกกลับไม่ใช้อาร์กิวเมนต์ใด ๆ คุณสามารถส่งการโทรกลับไปยังโดยตรงaddEventListenerแทนที่จะรวมไว้ในฟังก์ชันอื่น
อัฒภาคบางบรรทัดของคุณไม่มีอัฒภาค เพื่อให้สอดคล้อง stylistically ทั้งใช้พวกเขาหรือไม่ได้ - และหากคุณเลือกที่จะไม่หวังว่าคุณเป็นผู้เชี่ยวชาญด้านอื่น ๆ ที่คุณอาจทำงานได้ดีในปัญหาเกี่ยวกับการแทรกอัฒภาคอัตโนมัติ เลือกรูปแบบแล้วบังคับใช้กับlinter
idSubmitButton? ชื่อตัวแปรของปุ่มที่เลือกควรจะเป็นเหมือนsubmitButton- ID ไม่เกี่ยวข้องหลังจากที่เลือกแล้ว มีidเป็นคำนำหน้าในแอตทริบิวต์ ID submitButtonที่แปลกมากเกินไปอาจจะเพียงแค่ใช้
<button type="button" id="submitButton">Send and then Receive JSON</button>
<br>After clicking the button, the JSON string sent from the browser to the server will show in the console.
<br>Then the JSON response string from the server will replace what is below.
<br><br>
<div class='error' style='color: red; display: none;'></div>
<pre>Response gets inserted here</div>
<script>
'use strict';
const submitButton = document.getElementById('submitButton');
function exchangeJSON() {
submitButton.disabled = true; // assure post isn't sent again prior to a response
const testObjToSend = { message1: 'Test message from browser', message2: 'could be a complex JSON string' };
const errorDiv = document.querySelector('.error');
errorDiv.style.display = 'none'; // Hide previous error
// If you want to cause an error if the transaction is taking way longer than expected,
// see https://stackoverflow.com/q/46946380
fetch(
'ajaxTest1',
{
method: 'POST',
body: JSON.stringify(testObjToSend),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
},
)
.then(res => res.text()) // if you wanted to expand error handling, could check if response is OK first
.then((result) => {
try {
// If it's not JSON, this will throw
JSON.parse(result);
} catch(e) {
// Send text to catch handler below
throw new Error(result);
}
document.querySelector('pre').textContent = result;
})
.catch((error) => {
errorDiv.style.display = 'block';
errorDiv.textContent = JSON.stringify(error.message);
})
.finally(() => {
submitButton.disabled = false; // post completed, so enable posting again
});
}
submitButton.addEventListener('click', exchangeJSON);
</script>
ลงบนแบ็กเอนด์:
ให้บริการไฟล์แบบคงที่ด้วยโค้ด DRYขณะนี้ตัวจัดการการร้องขอและการตอบกลับสำหรับทั้งไฟล์ HTML และ Favicon ได้รับการฮาร์ดโค้ดลงในเซิร์ฟเวอร์ HTTP แม้ว่าจะสามารถทำงานได้แต่ก็ต้องใช้รหัสสำเร็จรูปจำนวนมากที่น่ารำคาญและเป็นเรื่องที่น่าเกลียด พิจารณาว่าคุณมีไฟล์คงที่ 4 หรือ 5 ไฟล์เพื่อให้บริการแทนหรือไม่ วิธีการปัจจุบันของคุณไม่สามารถปรับขนาดได้
ฉันชอบที่จะหลีกเลี่ยงห้องสมุดเว้นแต่จะมีเหตุผลที่น่าสนใจให้ใช้
นี่เป็นเหตุผลที่น่าสนใจมากที่จะใช้พวกเขา แม้ว่าจะเป็นเรื่องจริงที่พวกเขามักจะมาพร้อมกับคุณสมบัติมากมายที่ไม่สนใจ แต่ก็คุ้มค่าสำหรับคุณสมบัติที่ไม่สำคัญหนึ่งหรือสองหรือสามอย่างที่คุณจะต้องใช้งานด้วยตัวเองอย่างน่าเบื่อ
อันไหน? ฉันขอแนะนำ Express การใช้งานแพร่หลายมากและได้รับการบันทึกไว้อย่างดีในไซต์ของพวกเขาใน Stack Overflow และในที่อื่น ๆ ในอินเทอร์เน็ต
แยกเส้นทางออกเป็นไฟล์ต่างๆแม้ว่าฉันจะปรับโครงสร้างทั้งหมด แต่ด้านล่างสำหรับกรณีทั่วไปเมื่อคุณมีจุดสิ้นสุดที่ไม่คงที่หลายจุดบนเซิร์ฟเวอร์ที่ต้องจัดการตรรกะที่แตกต่างกันให้พิจารณาแยกเส้นทางต่างๆออกเป็นไฟล์ต่างๆ ตัวอย่างเช่นคุณสามารถมีไฟล์หนึ่งไฟล์ที่เอ็กซ์พอร์ตฟังก์ชันที่จัดการajaxTest1คำขอและอีกไฟล์หนึ่งที่เอ็กซ์พอร์ตฟังก์ชันที่จัดการloginคำขอ (ดังตัวอย่าง) เมื่อแอปพลิเคชันของคุณเติบโตขึ้นสิ่งนี้สามารถบำรุงรักษาได้มากกว่าการรวมทุกอย่างไว้ในไฟล์เดียว
'use strict';
const port = 8000;
const express = require('express');
// Recommended to use compression if you're transferring large files:
// https://github.com/expressjs/compression
const compression = require('compression');
const app = express();
app
.use(compression())
// Put static files into the "public" directory:
.use(express.static(__dirname + '/public'))
// Parse JSON request bodies:
.use(express.json())
.post('/ajaxTest1', (req, res) => {
console.log(req.body);
res.status(200).json({ data1: 'Test message from server', data2: 'could be a complex json object' });
})
.listen(port);
console.log(`Server is running on http://localhost:${port}. (^c to cancel)`);