日記
2018年明けましておめでとうございます。
更新が滞ってました。
年末にかけて仕事が多少忙しくなったこともあり、なかなかまとまって書く時間が取れなかったです。
新年は、39度の熱を出すという散々なスタートとなってしまいましたが、なんとか良い年にすべくがんばっていきたいと思います。
新年ということで、こっそり今年の目標を掲げることにしました。
年末にいくつクリアできたかな!とわくわくできるようにしたいです。
やることリスト
スキルセットにLaravel+Vue.jsと書けるようになる。
どれくらいできれば書いていいのかわからないのですが。なんかそろそろ書いてもいいかなという自信を持てるようになりたいです。
C言語で簡易Webサーバーを構築する
ずっと前からやろうやろうと思って、なかなか手がつけられてないです。
NginxとApacheであげられるC10k問題とかに対してもう少しちゃんと理解できるようにしておきたいです。
SPAで簡単なサイトをつくる
SPAである必要ってなんもないかもしれないんですが、ちょっとやってみたいです。
ChefやらDockerやらをちょっと使えるようにしておく。
Vagrantで仮想環境構築が少しだけわかるようになったので、もう一歩先にいきたいところです。
Go言語をさわってみる
理由はあんまりない。パラダイムシフトみたいなものを感じられらたらいいな。
ラズパイで2.4GHz帯のパケット解析
ドローンも買ったんだけれども自律飛行を目指す企画が頓挫しちゃってる。
これも進めたい。
ブログのアクセス数を倍にしたい
1日あたり200アクセスのところを目指せ400アクセスじゃ!
個人で案件を請け負ってみたい
一度くらいやってみたい。
LPICかAWSの資格あたりを一個とりたい
ピンキーと付き合いたい
本題
前回、オブジェクト指向フォームを作成しよう、でこんなかんじにaxiosを使って、データを送るコードを書きました。
www.tohuandkonsome.site
submit(){
for(let field in this.originalData){
this.originalData[field] = this[field];
}
axios.post('/thread', this.originalData)
.then(response => this.onSuccess(response.data))
.catch(error => this.onFail(error.response.data.errors));
}
このコードをベースにして、ファイルをサーバーに送る必要がでてきて、非同期やらコールバックやらPromiseやらaxiosやらasync/awaitやらでてきたので、備忘録がてら綴っていきたいと思います。
やりたいこと
- ブラウザからファイルを選択して、ファイルを読み込む
- ファイルの内容に問題がなければ、AWSのS3にアップロードする署名付きURLを発行する
- 署名付きURLに対してアップロードを行う
こんな感じのことをする必要がでてきました。
PHPでAWSの署名付きURLを発行するのも、いろいろと調べたのでいずれ書いておきたいのですが、ここでは以下のように非同期処理を順次処理をする、という点に注目して書いていきたいと思います。
- ブラウザからファイルを選択してファイルを読み込む
- post処理を行う
- 別のpost処理を行う
さっそくファイルを読み込む
まずは、ファイル読み込み処理を書いてみます。
ここでは、モジュールとして使いまわせるように`ReadFile.js'として切り出しておきます。
ReadFile.js
module.exports = function(file){
let reader = new FileReader();
reader.onload = function(){
text = reader.result
console.log('text:'+ text);
}
reader.readAsText(file)
}
FileReaderを使った読み込みですね。
そして、このFileReaderですが当然のように非同期で読み込みを行います。
なので、読み込みが完了したときにやっておきたいことは、reader.onload
に関数として書いておけばいけますね!
ここでは、単純に読み込んだテキストの内容をコンソールに出力しているだけです。
では、実際にReadFile.js
を使ってファイルを読み込んでみます。
前回に引き続き、VueインスタンスからReadFile.js
を使うことにします。
こんな感じの画面で、ファイル選択を押下すると
以下のapp.js
のonDropイベントが呼ばれて、コンソールにファイルの内容が出力される流れになっています。
app.js
const ReadFile = require('./components/ReadFile');
window.Vue = require('vue');
const app = new Vue({
el: '.simple-form',
methods:{
onDrop:function(event){
let file = event.target.files[0];
ReadFile(file);
}
},
});
ファイルの内容をチェックする
ファイルを読み込めたので、ファイルの内容をチェックします。
まず、普通に考えるとReadFileが読み込んだテキストの内容を返してくれて、それをもとにチェックするのがわかりやすいですよね!
なんだけれども、ReadFileはtextも返すようにはなっていません。
app.js
const ReadFile = require('./components/ReadFile');
window.Vue = require('vue');
const app = new Vue({
el: '.simple-form',
methods:{
onDrop:function(event){
let file = event.target.files[0];
let text = ReadFile(file);
console.log(text);
}
},
});
ちょっと`ReadFile.js'に視線を戻して、こんな感じにtextを返してよ!ってやっても返してはくれません。
ReadFile.js
module.exports = function(file){
let reader = new FileReader();
reader.onload = function(){
text = reader.result
console.log('text:'+ text);
return text
}
reader.readAsText(file)
}
というのも、ReadFile.js
の中の関数は、reader.readAsText(file)'が終わったら処理を戻してしまって、
reader.onload`は待たないからです。
(たぶんあってるんだけれども、シングルスレッドとかマルチスレッドとか、ちゃん理解できてないんだ。
reader.readAsText()
はファイルIOを行うのでIOを行っている間CPUは、別の処理を行っていて、ファイルIOが終わったら割り込みが走って、処理していたことを止めるなりなんやらして、reader.onload
で書かれていることが動いて、それが終わったら元の処理に戻る、みたいな感じなのかな。 )
全てはFileReaderが非同期で実装されているせいで、いろいろと頭を悩ませることになっています。
javascript以外の言語、C言語とかCOBOLとかだと同期(直列)処理が当たり前で、効率的に非同期でやりたい、って話がでてきてあれこれと悩むことがあると思うのですが、javascriptなんかは、非同期で実装されているものを、順番に実行したいみたいな逆の悩みがあって、どうしてこう違いがでるんだろう、みたいに思ってます。
javascriptのイベント駆動というワードに引っかかっていますが、未だにイベント駆動の意味がしっくりきません。.NETをさわってるときもイベント駆動みたいな話があったんだけれどもイベントってなんだろうなぁと。。。
話がそれました。
とりあえず、ReadFile.js
が読み込んだ値を返してくれない!チェック処理が書けない!という状態になってしまいます。
ええい、ままよ!と以下のように書くこともできます。
ここでは、ファイルの中に特定の文字が含まれるかを検査する処理を追加しています。
ReadFile.js
module.exports = function(file){
let reader = new FileReader();
reader.onload = function(){
text = reader.result
let count = (text.match(new RegExp('おはんき','g')) || []).length
if(count > 0){
console.log(count)
}
}
reader.readAsText(file)
}
途中までは、なんだいけるじゃん!と希望にあふれた船出でしたが、チェックがOKだった場合に続けざまに書いていく処理が、サーバー送信だったりして、ここに全部書くのか、、と不安になること間違いありません。
どうしよう、ということでまずとる手法がコールバック関数になります。
コールバック関数
コールバック聞いた当初は、やけに小難しいイメージがありましたが最近は少し慣れました。
単に関数を引数として渡すだけと思えば、そう大したことはありません。
さきほど、ReadFile.js
に書いた機能を関数として、app.js
に書いて、それを'ReadFile.js'に渡してあげます。
app.js
const ReadFile = require('./components/ReadFile');
window.Vue = require('vue');
const app = new Vue({
el: '.simple-form',
methods:{
onDrop:function(event){
let file = event.target.files[0];
const callback = function(text){
let count = (text.match(new RegExp('おはんき','g')) || []).length
if(count > 0){
console.log(count)
}
}
let text = ReadFile(file, callback);
},
});
ReadFile.js
では、受け取った関数を、reader.onload
が呼ばれるタイミングで実行してあげます。
ReadFile.js
module.exports = function(file){
let reader = new FileReader();
reader.onload = function(){
text = reader.result
callback(text)
}
}
reader.readAsText(file)
}
こうすることで、ReadFile.js'に書いていた処理を渡してあげることで、全部の処理を
ReadFile.js`に書く必要はなくなりましたね。
でも、ちょっとまってください。
これって結局、書く場所が変わっただけで、この後にサーバー送信をしたいってなったらどうなるんでしょう。
せっかくなんで書いてみることにしましょう。
AjaxでHTTPリクエストを行うxhr.js
を以下のように書いてみました。
xhr.js
module.exports = function(){
var xhr= new XMLHttpRequest();
xhr.open("GET","/sample");
xhr.send();
xhr.onload = function(){
if(xhr.readyState === 4 && xhr.status === 0) {
console.log(xhr.responseText);
}
};
}
本来はPOST処理を書くべきところなんですが、いろいろと面倒なのでシンプルにとあるAPIにGETリクエストを呼ぶだけになっています。
/samle
をGETすると、ohanky!
という文字列が返ってくるだけの素敵なAPIです。
xhrは例のごとく、当然のように非同期で処理がされるため、リクエストが受信し終わった場合の処理は、xhr.onload
に書いてあげます。
さて、こちらのサーバーリクエストは、さきほどのファイル読み込みのチェックが終わった場合に実行したい、としたとき、単純に考えるとこうなりますかね。
app.js
const ReadFile = require('./components/ReadFile');
const Xhr = require('./components/Xhr');
window.Vue = require('vue');
const app = new Vue({
el: '.simple-form',
methods:{
onDrop:function(event){
let file = event.target.files[0];
const callback = function(text){
let count = (text.match(new RegExp('おはんき','g')) || []).length
if(count > 0){
Xhr();
}
}
let text = ReadFile(file, callback);
},
});
callback関数の中に、サーバーリクエストを行うXhr()
を追加しています。
これで、ファイルを読み込んで内容に問題がなかったらサーバーリクエストを行う、ことができるようになりました。
コールバック関数地獄を体験する
さて、
- ファイルを読み込んで内容の確認を行う
- 内容に問題がなければサーバーリクエストを行う
ときたので、さらに
- ファイルを読み込んで内容の確認を行う
- 内容に問題がなければサーバーリクエストを行う
- サーバーリクエストに成功したら、別のサーバーリクエストを行う
といってみます。
サーバーリクエストの内容をファイルに書き込む、のほうがそれっぽいのですが、そもそもやりたかったことは、別のサーバーリクエストになるので、このままいきます。
今までのやり方を踏襲するのであれば、こんな感じでしょうか。
まずは、xhr.js
をcallback関数を受け取って実行するようにしときます。
(もちろんcallbackではなくxhr.onload
に書いてもいいんだけれども)
xhr.js
module.exports = function(callback){
var xhr= new XMLHttpRequest();
xhr.open("GET","/sample");
xhr.send();
xhr.onload = function(){
if(xhr.readyState === 4 && xhr.status === 0) {
console.log(xhr.responseText);
callback();
}
};
}
次に、xhr.js
に処理させたい関数をcallbackAfterRequest
という微妙な名前で作成しておき、Xhr()
関数に渡してあげます。
app.js
const ReadFile = require('./components/ReadFile');
const Xhr = require('./components/Xhr');
window.Vue = require('vue');
const app = new Vue({
el: '.simple-form',
methods:{
onDrop:function(event){
let file = event.target.files[0];
const callbackAfterRequest = function(){
var xhr= new XMLHttpRequest();
xhr.open("GET","/other");
xhr.send();
xhr.onload = function(){
if(xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
};
}
const callback = function(text){
let count = (text.match(new RegExp('おはんき','g')) || []).length
if(count > 0){
Xhr(callbackAfterRequest);
}
}
let text = ReadFile(file, callback);
},
});
これぐらいであれば、なんだ全然いけるじゃないか!と思うかもしれませんが、パッと見てどこがらどう処理が流れているのかがすごいわかりにくいです。
プログラムは上から下に流れると思いきや、後で動く処理が延々と書かれており、それがどのような順序で動くのかってなかなかわかりづらい印象を受けます。
ということで長くなったので次回Promise、そしてHTTPリクエストはaxiosを使って書いていきたいと思います。