ラボ > Javascript関連:ajax、form、イベント関連

js FetchAPIのメモ

ajaxじゃなくFetchAPI / jqueryを使わずにネイティブのJavascriptでいけるのがFetchAPIらしい。

作成日:2023-08-07, 更新日:2023-09-27

メモ

jqueryを使わずにネイティブのJavascriptでいけるのがFetchAPIらしい。
…ajaxもいけなかったっけ?

サンプル: 叩き台 / 2023-09-26

ファイル投稿も対応

class FetchHandler {
	constructor() {
		this.send_type_json = 'json';
		this.send_type_form = 'form';
		this.send_type_file = 'form_withFile';

		this.request_type = this.send_type_form;
		this.request_method = 'POST';
		this.request_header = {
			'Content-Type': 'application/json',
		};
		this.request_body = '';
		this.timeout = 15 * 1000;

		/* その他、初期化 */
	}

	connectionError(http_code) {
		if (http_code === 200) {
			return;
		}

		let message = '';
		if (http_code === 404) {
			message = 'Not Found';
		}
		else if (http_code === 500) {
			message = 'Internal Server Error';
		}
		else {
			message = 'Unknown Error';
		}
		// alert(message);
		throw new Error(message);
	}

	handleError(status) {
		/* エラー処理 */
	}

	handleSuccess(data) {
		/* 成功時の処理 */
	}

	convertRequestData_fromJson(request_data) {
		return JSON.stringify(request_data);
	}

	setRequest(request_data) {
		switch (this.request_type ) {
			case this.send_type_json:
				this.request_headers = {
					'Content-Type': 'application/json',
				};
				this.request_body = this.convertRequestData_fromJson(request_data);
			break;

			case this.send_type_form:
				this.request_headers = {
					'Content-Type': 'application/x-www-form-urlencoded',
					// 'Content-Type': 'multipart/form-data',
				};
				this.request_body = request_data;
			break;

			case this.send_type_file:
				this.request_headers = {
					'Content-Type': 'multipart/form-data',
				};
				this.request_body = request_data;
			break;

			default:
				throw new Error('Unknown Error');
		}
	}

	getFormValues(formElement) {
		let flg_formData = false;
		let formData;
		if ( this.request_type != this.send_type_json ) {
			flg_formData = true;
			formData = new FormData(formElement);
		}
		else {
			formData = {};
		}

		formElement.querySelectorAll('input, select, textarea').forEach(inputElement => {
			const name = inputElement.name;
			let value;

			if (inputElement.type === 'checkbox') {
				const checkboxValues = [];
				formElement.querySelectorAll('input[name="${name}"]:checked').forEach(checkbox => {
					checkboxValues.push(checkbox.value);
				});
				value = checkboxValues;
			}
			else if (inputElement.type === 'radio') {
				if (inputElement.checked) {
					value = inputElement.value;
				}
			}
			else if (inputElement.type === 'file') {
				if ( flg_formData ) {
					const fileInput = inputElement;
					const file = fileInput.files[0];
					formData.append(name, file, file.name);
				}
			}
			else {
				value = inputElement.value;
			}

			if (value !== undefined) {
				if ( flg_formData ) {
					formData.append(name, value);
				}
				else {
					formData[name] = value;
				}
			}
		});

		return formData;
	}

	async run(url, request_data) {
		try {
			const timeoutPromise = new Promise((resolve, reject) => {
				setTimeout(() => reject(new Error('Request timed out')), this.timeout);
			});

			this.setRequest(request_data);
			const response = await Promise.race([fetch(url, {
				method: this.request_method,
				// headers: this.request_headers, // ← コイツがいるとPHPの$_POSTが配列じゃなく文字列になってしまう…fetchに任せるのが良さげっぽい
				body: this.request_body,
			}), timeoutPromise]);

			this.connectionError(response.status);

			const data = await response.json();
			if (data.message !== undefined && data.message !== '') {
				this.handleError(data.message);
			}
			else {
				this.handleSuccess(data);
			}
		}
		catch (error) {
			console.error('ERROR:', error);
		}
	}
}

document.addEventListener('DOMContentLoaded', function() {
	const fetchHandler = new FetchHandler(); // インスタンス化
	document.querySelector('#form .submit').addEventListener('click', function() {
		// メソッド書換え
		fetchHandler.request_type = this.send_type_file;
		fetchHandler.request_method = 'POST';
		// fetchHandler.convertRequestData_fromForm = function(request_data) {
		// 	const formData = new FormData(request_data);
		// 	formData.append('abc', 'いろは');
		// 	formData.append('def', 'にほへ');
		// 	return formData;
		// }

		const url = 'your_php_file.php';

		const formElement = document.getElementById('form');
		const request_data = fetchHandler.getFormValues(formElement);
		// request_data.abc = 'いろは';
		// request_data.def = 'にほへ';
		fetchHandler.run(url, request_data);
	});
});

サンプル: 叩き台 / 2023-08-07

各メソッドを必要に応じて変更したいので、色々としておく。実際に動かしていないので動作するか不明。

class FetchHandler {
	constructor() {
		this.request_type = 'json';
		this.request_method = 'POST';
		this.request_header = {
			'Content-Type': 'application/json',
		};
		this.request_body = '';
		this.timeout = 15 * 1000;

		/* その他、初期化 */
	}

	connectionError(http_code) {
		if (http_code === 200) {
			return;
		}

		const message;
		if (http_code === 404) {
			message = 'Not Found';
		}
		else if (http_code === 500) {
			message = 'Internal Server Error';
		}
		else {
			message = 'Unknown Error';
		}
		// alert(message);
		throw new Error(message);
	}

	handleError(status) {
		/* エラー処理 */
	}

	handleSuccess(data) {
		/* 成功時の処理 */
	}

	convertRequestData_fromJson(request_data) {
		return JSON.stringify(request_data);
	}

	convertRequestData_fromForm(request_data) {
		return new FormData(request_data);
	}

	setRequest(request_data) {
		if (this.request_type == 'json') {
			this.request_headers = {
				'Content-Type': 'application/json',
			};
			this.request_body = this.convertRequestData_fromJson(request_data);
		}
		else if (this.request_type == 'form') {
			this.request_headers = {
				'Content-Type': 'application/x-www-form-urlencoded',
			};
			this.request_body = this.convertRequestData_fromForm(request_data)
		}
		else {
			throw new Error('Unknown Error');
		}
	}

	getFormValues(formElement) {
		const formValues = {};

		formElement.querySelectorAll('input, select, textarea').forEach(inputElement => {
			const name = inputElement.name;
			let value;

			if (inputElement.type === 'checkbox') {
				const checkboxValues = [];
				formElement.querySelectorAll('input[name="${name}"]:checked').forEach(checkbox => {
					checkboxValues.push(checkbox.value);
				});
				value = checkboxValues;
			}
			else if (inputElement.type === 'radio') {
				if (inputElement.checked) {
					value = inputElement.value;
				}
			}
			else {
				value = inputElement.value;
			}

			if (value !== undefined) {
				formValues[name] = value;
			}
		});

		return formValues;
	}

	async run(url, request_data) {
		try {
			const timeoutPromise = new Promise((resolve, reject) => {
				setTimeout(() => reject(new Error('Request timed out')), this.timeout);
			});

			this.setRequest(request_data);
			const response = await Promise.race([fetch(url, {
				method: this.request_method,
				headers: this.request_headers,
				body: this.request_body,
			}), timeoutPromise]);

			this.connectionError(response.code);

			const data = await response.json();
			if (data.status === 'error') {
				this.handleError(data.error);
			}
			else {
				this.handleSuccess(data);
			}
		}
		catch (error) {
			console.error('エラー:', error);
		}
	}
}

const fetchHandler = new FetchHandler(); // インスタンス化

// イベント処理
document.querySelector('#form .submit').addEventListener('click', function() {
	// メソッド書換え
	// fetchHandler.request_type = 'form';
	// fetchHandler.request_method = 'GET';
	// fetchHandler.convertRequestData_fromForm = function(request_data) {
	// 	const formData = new FormData(request_data);
	// 	formData.append('abc', 'いろは');
	// 	formData.append('def', 'にほへ');
	// 	return formData;
	// }

	const url = 'your_php_file.php';

	const formElement = document.getElementById('form');
	const request_data = this.getFormValues(formElement);
	// request_data.abc = 'いろは';
	// request_data.def = 'にほへ';
	fetchHandler.run(url, request_data);
});