JavaScriptのXHRライブラリにaxiosがあります。
axiosはPromiseが使えるので個人的に好きです。
JSCriptでもXHRが使えるのですが、そのままではaxiosを使うことができません。
なので今回はaxiosを少しカスタマイズして、JScriptからでも使えるようにしてみました。
babelを使ってES2015でJScriptを書こうなどを参考にJScriptでPromiseが使えるようにしておいてください。
Promiseのpolyfillを使ってもできるかもしれません。(未検証)
カスタムアダプタの設定
axios.createを呼ぶことで、カスタマイズした設定を反映したインスタンスを生成することができます。
axiosはadapterと呼ばれるモジュールで実際の通信を行います。
設定のadapter
にカスタマイズしたadapterを指定すると、通信時に優先的に呼ばれるようになります。
以下のコードはaxios v0.13のlib/adapters/xhr.js
をベースに改変しています。
カスタムアダプタの仕様が変わると使えなくなるかもしれません。
var axios = axios.create({
adapter: function(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers;
if (utils.isFormData(requestData)) {
delete requestHeaders['Content-Type']; // Let the browser set it
}
var request = new ActiveXObject('MSXML2.ServerXMLHTTP');
// HTTP basic authentication
if (config.auth) {
var username = config.auth.username || '';
var password = config.auth.password || '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}
try {
request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true);
// Listen for ready state
request.onreadystatechange = function handleLoad() {
if (!request || (request.readyState !== 4)) {
return;
}
// The request errored out and we didn't get a response, this will be
// handled by onerror instead
if (request.status === 0) {
return;
}
// Prepare the response
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.responseBody;
var response = {
data: responseData,
// IE sends 1223 instead of 204 (https://github.com/mzabriskie/axios/issues/201)
status: request.status === 1223 ? 204 : request.status,
statusText: request.status === 1223 ? 'No Content' : request.statusText,
headers: responseHeaders,
config: config,
request: request
};
settle(resolve, reject, response);
// Clean up request
request = null;
};
// Add xsrf header
// This is only done if running in a standard browser environment.
// Specifically not if we're in a web worker, or react-native.
if (utils.isStandardBrowserEnv()) {
var cookies = require('axios/lib/helpers/cookies');
// Add xsrf header
var xsrfValue = config.withCredentials || isURLSameOrigin(config.url) ?
cookies.read(config.xsrfCookieName) :
undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
// Add headers to the request
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
// Remove Content-Type if data is undefined
delete requestHeaders[key];
} else {
// Otherwise add header to the request
request.setRequestHeader(key, val);
}
});
}
if (requestData === undefined) {
requestData = null;
}
// Send the request
request.send(requestData);
// Set the request timeout in MS
request.waitForResponse(config.timeout);
if (request.readyState !== 4) {
request.abort();
reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED'));
request = null;
}
} catch (err) {
reject(createError(err.description, config, (err.number >>> 0).toString(16)));
// Clean up request
request = null;
}
});
},
});
主に変更した点は以下になります。
-
new XMLHttpRequest();
をnew ActiveXObject('MSXML2.ServerXMLHTTP');
に変更 - timeoutの設定をsetTimeoutsメソッドを使った方法に変更
- XDomainRequestに関する処理を省いた
- xhrのonerrorとontimeoutにメソッドを設定しないようにした
- withCredentials、responseType、progressに関する設定をしないようにした
- xhrのopenからsendまでをtry-catchでくくるようにした
- catchした時にエラーメッセージとエラーコードを設定するようにした
JScriptで利用できるXHRは設定可能なプロパティが少ないため、その部分を削っています。
全体をtry-catchでくくっているのは、
本来、通信が失敗した場合はonerrorやontimeoutが呼ばれますが、JScriptではこれらを設定することができません。
JScriptではonerrorやontimeoutを呼ぶ代わりに例外を投げるので、try-catchでくくってあげています。
axiosを使った通信
先ほど生成したaxiosのインスタンスで通信を行う処理を書いてみます。
// 上のコードの続き
axios.defaults.baseURL = 'http://jsonplaceholder.typicode.com';
axios.get('/posts/1').then((res) => {
WScript.Echo(res.data.title);
WScript.quit();
})['catch']((err) => {
if (err.code) {
WScript.Echo(err.code);
}
WScript.Echo(err.message);
WScript.quit();
});
for (;;) {
WScript.Sleep(100);
}
C:\wsh>cscript hoge.js
Microsoft (R) Windows Script Host Version 5.7
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.
sunt aut facere repellat provident occaecati excepturi optio reprehenderit
注意点としては、リクエストを送ったあとには何か他の処理をしていないと、レスポンスが返ってくる前に終了してしまいます。
そのため、ここでは無限にsleepをして、リクエストが返ってきたらquitしてあげています。
また、Promiseのcatchは上のように書いてあげる必要があります。
IE9未満ではcatchが構文エラーになるようです。
transform-es3-member-expression-literalsを適用することで、今までながらの書き方をすることもできます。
おわりに
JScriptも頑張れば最近のライブラリを使いまくれるのでいいですね。
需要はアレですが、何かの参考になるとうれしいです。