tag:crieit.net,2005:https://crieit.net/tags/Promise/feed 「Promise」の記事 - Crieit Crieitでタグ「Promise」に投稿された最近の記事 2019-06-09T23:58:56+09:00 https://crieit.net/tags/Promise/feed tag:crieit.net,2005:PublicArticle/15080 2019-06-09T23:58:56+09:00 2019-06-09T23:58:56+09:00 https://crieit.net/posts/fetch-fetch fetchがよくわからなかったのでオレオレfetchを作って雰囲気を掴む <p>前回、<a href="https://crieit.net/posts/Promise-Promise">こんな記事</a>を書きました。<br /> 上の記事のそもそもの発端は、fetchを使おうとしたけども、よくわからなかったからというものでした。<br /> 根本的には、fetchがわからないというよりはPromiseがよくわかってなかったという方が近いので、前回Promiseの雰囲気を掴んだ時点で7割方解決しているのだけれど、そもそもの発端を解決させようと思います。</p> <h2 id="そもそもの発端"><a href="#%E3%81%9D%E3%82%82%E3%81%9D%E3%82%82%E3%81%AE%E7%99%BA%E7%AB%AF">そもそもの発端</a></h2> <p>超絶ざっくり書くと、fetchというのは下記のようになるかと。</p> <pre><code class="JavaScript">fetch(url) .then((response)=>{ return response.json(); }) .then(()=>{ //json使って何か処理 }) .catch((error)=>{ //エラー処理 }) </code></pre> <p>まあ、こんな感じのものを見て、非同期をよくわかっていなかった僕はちんぷんかんぷんになって、どうにか、fetchというのは非同期で動くものらしい、というところまではわかった。<br /> なのでどうにかして非同期を勉強しなければいけない。<br /> そこでググったら出てくるわけです。</p> <pre><code class="JavaScript">const hoge = ()=>{ return new Promise((resolve,reject)=>{ setTimeout(()=>{ console.log('wait 3 sec!') resolve('wait 3 sec!') },3000) }) } </code></pre> <p>……。さっきまでPromiseとかresolveとかrejectとか出てこなかったじゃん。<br /> ってなって挫折していた。<br /> 今思えば至極単純で、Promiseとかresolveとかrejectとかはfetchの中で勝手にやっていることなので、fetchを使う分には登場しない。<br /> もちろん、理解する必要はあって、そのために今回もオレオレfetchを作ることで雰囲気を掴もうと思う。</p> <h2 id="注意"><a href="#%E6%B3%A8%E6%84%8F">注意</a></h2> <p>あくまで雰囲気を掴むだけです。実際の実装は微塵も知りません。<br /> いろんなドキュメントを読んだ上での筆者の解釈(を筆者の実装レベルでできるとこだけ再現したもの)です。とんちんかんかもしれません。<br /> 妥協しまくりです。<br /> あまりにもとんちんかんなこと言ってたら教えて下さい。<br /> 信用しないでください。<br /> レベル不足でクソコードです。</p> <h2 id="まず呼び出すコードから"><a href="#%E3%81%BE%E3%81%9A%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99%E3%82%B3%E3%83%BC%E3%83%89%E3%81%8B%E3%82%89">まず呼び出すコードから</a></h2> <p>こんな感じのjsonを作ります。<br /> json-serverとかで適当にapiを作ります。</p> <pre><code class="json">{ "wizards":[ { "id":1, "name":"Arietta", "feature":"monster" }, { "id":2, "name":"Relum", "feature":"fool" }, { "id":3, "name":"Elise", "feature":"poor" } ] } </code></pre> <p>で、呼び出し側</p> <pre><code class="html"><html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script type="text/javascript" src="promise.js"></script> <script type="text/javascript" src="fetch.js"></script> <script type="text/javascript" src="index.js"></script> <style> table{ border:solid 1px; } th{ border:solid 1px; } td{ border:solid 1px; } </style> </head> <body> hello async <button onclick="getData()"> fetch </button> <table > <thead> <th>id</th> <th>name</th> <th>feature</th> </thead> <tbody id="tbody"> </tbody> </table> </body> </html> </code></pre> <pre><code class="JavaScript">const getData = () => { const url = 'https://localhost:3000/wizards' //json-serverとかでよしなに fetch(url) //oreoreFetch(url) //これに置き換えても動くようにするのが目標 .then((response)=>{ return response.json(); }) .then((json)=>{ const tbody = document.getElementById('tbody'); json.forEach((wiz)=>{ let tr = ` <tr> <td>${wiz.id}</td> <td>${wiz.name}</td> <td>${wiz.feature}</td> </tr> `; tbody.insertAdjacentHTML("beforeend",tr); }); }) } </code></pre> <p><a href="https://crieit.now.sh/upload_images/59edc21d3f7f537d3dfb5ec99e2cfb135cf9c8d15ca25.JPG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59edc21d3f7f537d3dfb5ec99e2cfb135cf9c8d15ca25.JPG?mw=700" alt="WS000001.JPG" /></a><br /> まー、動くよね</p> <h2 id="で、fetchのコード"><a href="#%E3%81%A7%E3%80%81fetch%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89">で、fetchのコード</a></h2> <pre><code class="JavaScript">const oreoreFetch = (url)=>{ return new Promise((resolve,reject)=>{ httpRequest(resolve,reject,url) }); //最終的に前回のオレオレPromiseで動いてほしい //けど、Promiseチェーンが実装できなかったんだよな… //return new oreorePromise((resolve,reject)=>{ // httpRequest(resolve,reject,url) //}); } const httpRequest = (resolve,reject,url) =>{ const xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { switch ( xhr.readyState ) { //めんどいので4以外省略 case 0: break; case 1: // データ送信中. break; case 2: // 応答待ち. break; case 3: // データ受信中. break; case 4: // データ受信完了. if( xhr.status == 200 || xhr.status == 304 ) { const data = new oreoreResponse(xhr.responseText); resolve(data); } else { reject(xhr.statusText); } break; } } xhr.open( 'GET', url, true ); xhr.send(null); } class oreoreResponse{ constructor(data){ this.data = data; } json(){ return JSON.parse(this.data); } } </code></pre> <p>fetchができればいいのでResponseクラスは適当です。<br /> (実際はコンストラクタに引数はないし、jsonメソッドはPromiseを返す)<br /> Promiseの勉強で出てきたresolveとかrejectは(たぶん)fetchの中にあって、よしなにやってくれるので、fetchを使う分には意識しなくていいよ、ってのがわかる。</p> <p>ということで動かす。</p> <pre><code class="JavaScript">const getData = () => { const url = 'https://localhost:3000/wizards' //fetch(url) oreoreFetch(url) //これに置き換えても動くようにするのが目標 .then((response)=>{ return response.json(); }) .then((json)=>{ const tbody = document.getElementById('tbody'); json.forEach((wiz)=>{ let tr = ` <tr> <td>${wiz.id}</td> <td>${wiz.name}</td> <td>${wiz.feature}</td> </tr> `; tbody.insertAdjacentHTML("beforeend",tr); }); }) } </code></pre> <p><a href="https://crieit.now.sh/upload_images/8b94c3234843b3b4306df2d82355801c5cf9c8ec8f968.JPG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8b94c3234843b3b4306df2d82355801c5cf9c8ec8f968.JPG?mw=700" alt="WS000000.JPG" /></a><br /> まー、それっぽく動く。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>みんな、async/await使おうぜ!</p> hammhiko tag:crieit.net,2005:PublicArticle/15072 2019-06-07T00:25:00+09:00 2019-06-07T07:58:46+09:00 https://crieit.net/posts/Promise-Promise PromiseがよくわからなかったのでオレオレPromiseを作って雰囲気を掴む <p>非同期がわかりません。<br /> 「この関数はPromiseを返します」<br /> Promiseって何だよ。<br /> 非同期やってもコールバック地獄にならないらしいけど、<br /> 結局引数に渡すのはコールバックだし、<br /> 僕からするとまーまーわからんのです。</p> <p>というわけでPromiseを自作して雰囲気を掴むことにしました。</p> <h2 id="注意"><a href="#%E6%B3%A8%E6%84%8F">注意</a></h2> <p>あくまで雰囲気を掴むだけです。実際の実装は微塵も知りません。<br /> いろんなドキュメントを読んだ上での筆者の解釈(を筆者の実装レベルでできるとこだけ再現したもの)です。とんちんかんかもしれません。<br /> 妥協しまくりです。<br /> Promise.allとかやりません。それどころかPromiseチェーンすらできません。<br /> catchは実装したけど試してません。<br /> (要はほとんどできてないじゃないか)</p> <p>あまりにもとんちんかんなこと言ってたら教えて下さい。<br /> 信用しないでください。<br /> レベル不足でクソコードです。</p> <h2 id="とりあえずコード"><a href="#%E3%81%A8%E3%82%8A%E3%81%82%E3%81%88%E3%81%9A%E3%82%B3%E3%83%BC%E3%83%89">とりあえずコード</a></h2> <p>とりあえずよく呼び出す側のコードです。</p> <pre><code class="html"><html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>オレオレPromise</title> <script type="text/javascript" src="promise.js"></script> <script type="text/javascript" src="index.js"></script> </head> <body> hello async <button onclick="wait()"> async start </button> <p id="start" style="display:none"></p> <p id="async" style="display:none"></p> <p id="finish" style="display:none"></p> </body> </html> </code></pre> <pre><code class="JavaScript">const wait = ()=>{ const start = document.getElementById('start'); start.style=""; start.innerHTML += "start:" + new Date(); hidouki().then(finish).catch(dispError); } const hidouki = ()=>{ return new Promise(wait1); //return new oreorePromise(wait1); //こっちに書き換えても動くようになるのが目標 } const wait1 = (resolve,reject) => { setTimeout(() => { const async1 = document.getElementById('async'); async1.style=""; async1.innerHTML += "wait3 sec :" + new Date(); resolve('succeeded async'); },3000); } const finish = (value) => { setTimeout(()=>{ const finish = document.getElementById('finish'); finish.style = ""; finish.innerHTML += value + " :" + new Date(); },5000); } const dispError = (error) => { alert(error); } </code></pre> <p><a href="https://crieit.now.sh/upload_images/7582dd8ee85437bbc45010a0c82f6a3d5cf92fb6c3701.JPG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7582dd8ee85437bbc45010a0c82f6a3d5cf92fb6c3701.JPG?mw=700" alt="WS000004.JPG" /></a><br /> ちゃんと待ってる。</p> <p>さて、new Promiseしているところを自作Promiseに書き換えても動くようにするのが目標です。</p> <h2 id="わからんなりの実装"><a href="#%E3%82%8F%E3%81%8B%E3%82%89%E3%82%93%E3%81%AA%E3%82%8A%E3%81%AE%E5%AE%9F%E8%A3%85">わからんなりの実装</a></h2> <p>正直、Promiseのコンストラクタの引数はコールバック関数で、そのコールバック関数の引数はresolveとrejectで……、<br /> って言われてわかる? みんなすごいね! 僕はわかんないです。そのresolveとrejectはどこから来たんだよ。<br /> ってのを試行錯誤しながら理解しようとした結果、以下のようになりました。</p> <pre><code class="JavaScript">class oreorePromise{ constructor(callback){ this.value = null; this.state = 'pending'; this.thenTimer = []; this.catchTimer = []; callback(this.resolve.bind(this),this.reject.bind(this)); } resolve(value){ this.value = value; this.state ='fulfilled'; } reject(value){ this.value = value; this.state='rejected'; } then(nextCallback){ this.thenTimer.push(setInterval(function(){ if(this.state === 'fulfilled'){ this.value = nextCallback(this.value); clearInterval(this.thenTimer.shift()); } else if(this.state === 'rejected'){ //待機やめる clearInterval(this.thenTimer.shift()); } }.bind(this),100)); //テストだし100ミリ秒くらい待とう // https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises#Chaining // then 関数は元の Promise とは別の新しい Promise を返します。 // らしいけど、もう力尽きた…。 return this; } catch(errorCallback){ this.catchTimer.push(setInterval(function(){ if(this.state === 'fulfilled'){ //待機やめる clearInterval(this.catchTimer.shift()) } else if(this.state === 'rejected'){ errorCallback(this.value); clearInterval(this.catchTimer.shift()) } }.bind(this),100)); } } </code></pre> <p>JavaScriptのthisがわかんなくて、ただでさえわからないのにコールバックしまくってthisが迷子になって結構しんどかった。一応動いたけど。<br /> <strong>Promiseよりthisの方が難しかった。</strong><br /> だれかthis教えて。</p> <p>resolveとrejectはPromiseオブジェクトが持つメソッドで、<br /> 「コンストラクタに引数として渡したコールバック関数を実行するときに、resolveとrejectを引数として実行しますよ。<br /> なので、実行したい関数を定義するときにresolveとrejectが引数になるように定義してくださいね」<br /> という<strong>お約束</strong>だということが自分で書いてようやくわかった。</p> <p>わかってからだと、ちゃんと<a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise#Parameters">書いてある</a>ことがわかる。なぜ最初から見つけられないのだ。</p> <p>どこかの解説記事で、<br /> 「resolve関数を実行するとthenメソッドが呼び出される」<br /> とか書いてあって腑に落ちなかったんだけど、たぶん、正しいのは、<br /> 「thenメソッドは即座に呼ばれていて、オブジェクトの持つ状態(oreorePromiseではプロパティstate)がfulfilledになるまで、引数として渡されたコールバック関数の実行を待つ」<br /> なんだと思う。<br /> (自信はない)<br /> resolveは状態を変更しているだけで、別にthenに渡された関数を実行しているとかはない、と思う。(ここは特に自信ない)</p> <p>最初にそう言う説明を見てしまったのでthenメソッドが魔法のように見えてしまっていたのだけれど、<br /> (MDNで「<a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises#Chaining">さあ魔法の時間です。</a>」とか言ってるしね!)<br /> よく見ればobject.method()という至極基本的な文法だし、即座に呼ばれて当然だよなと。</p> <p>このコードでは、状態が変わるまでsetIntervalでループしてるのは最高にダサいと思うけど、本質とずれるのであまり凝ってない。</p> <h2 id="できたので書き換えてみる"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%81%AE%E3%81%A7%E6%9B%B8%E3%81%8D%E6%8F%9B%E3%81%88%E3%81%A6%E3%81%BF%E3%82%8B">できたので書き換えてみる</a></h2> <pre><code>const hidouki = ()=>{ //return new Promise(wait1); return new oreorePromise(wait1); //こっちに書き換えても動くようになるのが目標 } </code></pre> <p><a href="https://crieit.now.sh/upload_images/b839e5e7f02980a5b56afc20fd54d5625cf92fd3dacfc.JPG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b839e5e7f02980a5b56afc20fd54d5625cf92fd3dacfc.JPG?mw=700" alt="WS000005.JPG" /></a></p> <p>動いた。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>ドキュメントを見返して、ソースを見返す度に「あ、ここ違うな」ってなる。<br /> ただ、重ね重ねだけど、雰囲気を掴んでPromiseを使えるようになるのが目的で、Promiseのコピーを作りたいとかではない。<br /> せめてPromiseチェーンは実装したかったけど、費用対効果が薄すぎてやめた。<br /> 個人的には</p> <blockquote> <p>resolveとrejectはPromiseオブジェクトが持つメソッドで、<br /> 「コンストラクタに引数として渡したコールバック関数を実行するときに、resolveとrejectを引数として実行しますよ。<br /> なので、実行したい関数を定義するときにresolveとrejectが引数になるように定義してくださいね」<br /> という<strong>お約束</strong>だということが自分で書いてようやくわかった。</p> <p>どこかの解説記事で、<br /> 「resolve関数を実行するとthenメソッドが呼び出される」<br /> とか書いてあって腑に落ちなかったんだけど、たぶん、正しいのは、<br /> 「thenメソッドは即座に呼ばれていて、オブジェクトの持つ状態(oreorePromiseでは変数名state)がfulfilledになるまで、引数として渡されたコールバック関数の実行を待つ」<br /> なんだと思う。<br /> (自信はない)</p> </blockquote> <p>ここが理解できた時点でもういいかって感じ。おまけで完成させた。</p> <h2 id="まとめ2"><a href="#%E3%81%BE%E3%81%A8%E3%82%812">まとめ2</a></h2> <p>みんな、async/await使おうぜ!</p> <h2 id="まとめ3"><a href="#%E3%81%BE%E3%81%A8%E3%82%813">まとめ3</a></h2> <p>だれかthis教えて</p> hammhiko tag:crieit.net,2005:PublicArticle/14804 2019-02-16T11:42:15+09:00 2019-02-16T12:50:42+09:00 https://crieit.net/posts/bc030f9bc7008b5c0eb13a9cc3989831 非同期がわからないのはゲシュタルト崩壊を起こしていたからだ <p>ということに気が付いた。</p> <p>すげーあほなこといってます。</p> <h1 id="よくある?サンプル"><a href="#%E3%82%88%E3%81%8F%E3%81%82%E3%82%8B%EF%BC%9F%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB">よくある?サンプル</a></h1> <pre><code>fetch(url) .then(function(response){ //fetchで返ってきたデータをjson形式にする return response.json(); }) .then(function(json){ //jsonで何か処理する doSomething(json); }) .catch(function(error){ //エラー発生 console.log(error); }) </code></pre> <p>(async/await使えよって指摘はごもっともなんですが……)<br /> 初見で見たときすごい未知の呪文に見えたんですね。<br /> で、なんとなく真似てやってみても、ちょっと変えようとすると動かない、みたいな。<br /> めっちゃ変な書き方しちゃう、みたいな。</p> <p>「thenはメソッドで引数にコールバックを指定します。」<br /> ただこれだけなのに、なぜか謎の呪文に見えていた時期があった。</p> <h1 id="アホの極み"><a href="#%E3%82%A2%E3%83%9B%E3%81%AE%E6%A5%B5%E3%81%BF">アホの極み</a></h1> <p>というか今までthenのメソッドチェーンが特殊な記法に見えていたんだけど、これって<br /> <strong>(fetchが返す)Promiseオブジェクトが持つthenメソッド</strong><br /> じゃん。普通の書き方じゃん。A.B()じゃん。PHP風に書くとA->B()じゃん。<br /> え、なんでこんなこと今まで気づかなかったの、俺。馬鹿じゃん。</p> <p>たぶん、メソッドの引数がコールバックで、しかも無名関数で書かれているから、その時点でちょっと複雑になって、<br /> しかも<strong>括弧と波括弧とfunctionがゲシュタルト崩壊</strong>起こして謎の呪文に見えていたんだな。きっと。<br /> 実際はもっと処理が書いてあって複雑だし。</p> <p>あと、メソッドの部分で改行するのも馴染みがない。<br /> そりゃ、長くなるからわかるんだけども、なんか、ねえ……。<br /> だって、</p> <pre><code>mojiretsu .substring(0,5); </code></pre> <p>とか普通書かないじゃん。<br /> (意外と書くのかな?)</p> <p>なんかこの辺、コーディング規約とかあるのかな?</p> <h1 id="ということで書き直す"><a href="#%E3%81%A8%E3%81%84%E3%81%86%E3%81%93%E3%81%A8%E3%81%A7%E6%9B%B8%E3%81%8D%E7%9B%B4%E3%81%99">ということで書き直す</a></h1> <pre><code>function convertToJson(response){ //fetchで返ってきたデータをjson形式にする return response.json(); } function doSomething(json){ //json使って何か処理する doSomething(json); } function addErrorLog(error){ //エラー発生 console.log(error); } fetch(url).then(convertToJson).then(doSomething).catch(addErrorLog); </code></pre> <p>(アロー関数使えって指摘はごもっともなんですけどね……)<br /> これならもっと理解早かったわ、きっと。<br /> まあ、この次にPromiseとかコールバックを理解するっていう壁があるんですけどね。<br /> thenメソッドに渡したコールバック関数の引数って何だよ、とか。<br /> その辺は別の記事で書くかもしれない。</p> <h1 id="教訓"><a href="#%E6%95%99%E8%A8%93">教訓</a></h1> <p>サンプルがわからないときは自分がわかるように分解・再構築するとわかりやすいかも。<br /> アロー関数がわからない(慣れてない)ならfunctionに直すとか。<br /> (アロー関数は覚える必要があるという前提で。というか、こういう作業をやっていくと覚えやすいのでは)<br /> 無名関数でゲシュタルト崩壊起こすなら、別関数に切り出すとか。<br /> あとは、まあ、いろいろ。</p> <p>まあ、わからないから勉強しようとして、それがわからないんだから、直すのも一苦労だろうけど。<br /> わかるところから分解するしかないよなー。</p> <p>当たり前だけど、自分で手を動かさなきゃわからんよね。</p> <p>以上</p> hammhiko