以下のコードを用いてライブラリやPromiseなどを使わずに、5つのurlに対して次々にほぼ同時にリクエストを送って、「1.txt、2.txt、3.txt、4.txt、5.txt」と指定した順番にレスポンス結果を出力し、最後に「all done!」と出力する為には、みなさんならどのような書き方をしますか?
注意点としては、1つの目のリクエストのレスポンスを受けた後に、次のリクエストを送るといった順列処理ではなく、最初に5つのリクエストをほぼ同時に行って、指定の順序でレスポンスを出力するというところです。
補足:KSwordOfHasteさんの回答にあったように、**「結果が揃っている部分だけはできるだけ早く(しかし順番を守って)処理したい」**という想定です。
また、以下に掲載するコードを使ってください。どうしても使いたくない場合は、それはそれでOKとします。。。以下の掲載コードにある関数外にコードを書くのはOKです。
また、前述の通り、ライブラリやPromiseなどを使うのはNGとします。
尚、自分も後で自分なりのやり方を投稿しますが、やり方を教えて欲しいといった類の質問ではなく、どういったやり方をみなさんがされるかに興味があるため、「コードを載せてください」といった趣旨に沿わないコメントは不要です。
JavaScript
1// ajaxのフェイクバージョンのつもり 2function fakeAjax(url, callback){ 3 //レスポンスまでのランダムなタイムラグをシミュレート 4 var time = Math.floor(Math.random()*(3000-1000)+1000); 5 var responseMock = { 6 "1.txt": "1st request", 7 "2.txt": "2nd request", 8 "3.txt": "3rd request", 9 "4.txt": "4th request", 10 "5.txt": "5th request" 11 }; 12 setTimeout(() => { 13 callback(responseMock[url]); 14 }, time); 15} 16 17function getFile(url){ 18 fakeAjax(url, function(response){ 19 // 20 }); 21} 22 23getFile("1.txt"); 24getFile("2.txt"); 25getFile("3.txt"); 26getFile("4.txt"); 27getFile("5.txt");
期待するログ出力の結果(console.log)
1st request 2nd request 3rd request 4th request 5th request all done!
補足
低評価を押す場合は理由を添えてください。マイナス評価が多いにも関わらず、質問の趣旨に沿った物凄く有益な回答を頂いており、価値あるページになっていると思います。あえて、質問の趣旨に意図的に沿わない回答をする必要はないかと思いますので、的外れな回答はお控えください。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/11/20 02:39
退会済みユーザー
2017/11/20 14:04
2017/11/22 09:11
退会済みユーザー
2017/11/23 14:27
2017/11/24 12:59
退会済みユーザー
2017/11/25 10:10
2017/11/27 05:04
回答9件
0
そもそもteratailは有益な情報を共有する場であって、
質問者のためのオーダメイドのコードを書く場とは違います。
低評価が付いてるのも、そういう理由からだと思います。
実務では「ライブラリやPromise」を素直に使えば良いと思います。
ただ、それ以外の方法もあるので、一応書いてみました。
上記の理由で、ご質問の細かい仕様はあえて無視し、
不特定多数で共有しやすいように、シンプルに書きました。
javascript
1function fakeAjax(g) { 2 var time = Math.floor(Math.random() * 2000 + 1000); 3 setTimeout(function() { 4 console.log(g.next()); 5 }, time); 6} 7 8var g = (function*() { 9 yield "1st request"; 10 yield "2nd request"; 11 yield "3rd request"; 12 yield "4th request"; 13 yield "5th request"; 14 return "all done!"; 15})(); 16 17for (i = 0; i < 6; i++) { 18 fakeAjax(g); 19}
ほぼES6前提ですが、「Generator」を使うと、
コールバック地獄のような深いネストを避けて、
非同期処理を非常にシンプルに書くことができます。
なお余談ですが、RubyやPythonをやっていると、
「yield」や「Generator」のような概念は馴染み深いです。
投稿2017/11/19 12:37
総合スコア5592
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2017/11/19 12:47 編集
退会済みユーザー
2017/11/19 12:43
2017/11/19 13:28
2017/11/19 14:03
退会済みユーザー
2017/11/19 14:30
2017/11/19 15:13 編集
退会済みユーザー
2017/11/20 00:51
0
つまり、変なコード書いた奴が一等賞ってことですね!
負けないぞう!
javascript
1function fakeAjax(url, callback){ 2 //レスポンスまでのランダムなタイムラグをシミュレート 3 var time = Math.floor(Math.random()*(3000-1000)+1000); 4 var responseMock = { 5 "1.txt": "1st request", 6 "2.txt": "2nd request", 7 "3.txt": "3rd request", 8 "4.txt": "4th request", 9 "5.txt": "5th request" 10 }; 11 setTimeout(() => { 12 callback(responseMock[url]); 13 }, time); 14} 15 16function getFile(url){ 17 q[url] = null; 18 fakeAjax(url, function(response){ 19 q[url] = response; 20 [...q].forEach( x => console.log(x) ); 21 }); 22} 23 24const q = new Proxy([],{ 25 get: function(obj, prop) { 26 if (prop===Symbol.iterator) { 27 if (obj.length) return function*(){ 28 let x; 29 while( x = obj.shift() ) { 30 if (x.v) 31 yield x.v 32 else { 33 obj.unshift(x); 34 break; 35 } 36 } 37 if (!x) yield 'all done!'; 38 }; 39 } 40 return obj.filter( x => x.k == prop )[0].v; 41 }, 42 set: function(obj, prop, value){ 43 let entry = obj.filter( x => x.k == prop )[0]; 44 if (entry) 45 entry.v = value 46 else 47 obj.push({k:prop, v:value}); 48 }, 49}); 50 51getFile("1.txt"); 52getFile("2.txt"); 53getFile("3.txt"); 54getFile("4.txt"); 55getFile("5.txt");
不要と思いますが解説。
[{k:'1.txt', v:'1st request'}, ...]
のような構造体を作り、Proxy
により独自アクセサを設定することによって、順序型であることを意識せずにプロパティにアクセスするようにしました。
また、Symbol.iterator
のgetter
に出力の際のめんどくさい処理を設定することにより、列挙するだけで出力が得られるようになっています。
その結果、元コードに追加したのは3行のみ、というシンプルなコードになったと思います。
(Falsyな値が来るといろいろバグりますけど)
投稿2017/11/20 09:57
編集2017/11/20 11:07総合スコア35865
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2017/11/20 14:05
2017/11/20 16:02
2017/11/21 05:30
2017/11/21 11:42
0
maisumakunさんやLLmanさんのおっしゃることはもっともであると思いましたが、Javascriptの練習のつもりでやってみました。
本件に特化した単純な実装を想定すると、Promise.all(...).then(...)
でthenのところで、全ての結果を出力するようなものの特化版(下記)をイメージしましたが、
リスト1:
javascript
1const dbg = m => console.log('information: ' + m); 2 3function fakeAjax(url, callback){ 4 var time = Math.floor(Math.random()*(300-100)+100); 5 var responseMock = { 6 "1.txt": "1st request", 7 "2.txt": "2nd request", 8 "3.txt": "3rd request", 9 "4.txt": "4th request", 10 "5.txt": "5th request" 11 }; 12 13 setTimeout(() => { 14 dbg('completed ' + url); 15 callback(responseMock[url]); 16 }, time); 17 dbg('submitted ' + url); 18} 19 20function getFiles(urls) { 21 var n = urls.length, res = new Array(n), 22 i, responseCount = 0; 23 for (i in urls) { 24 getFile(i, urls[i]); 25 } 26 27 function getFile(i, url) { 28 fakeAjax(url, function (response) { 29 res[i] = response; 30 if (++responseCount >= n) 31 allDone(); 32 }); 33 } 34 35 function allDone() { 36 var i; 37 for (i in urls) { 38 console.log(res[i]); 39 } 40 console.log("all done!"); 41 } 42} 43 44getFiles(["1.txt", "2.txt", "3.txt", "4.txt", "5.txt"]);
しかしこれだとPromise.allを使わない根拠がない気がしたので、「結果が揃っている部分だけはできるだけ早く(しかし順番を守って)処理したい」という想定を置くと
リスト2:
javascript
1// fakeAjaxは省略 2// 若干の見易さのためにES2015の文法を用いました 3 4class AsyncContext { 5 constructor() { 6 this.acceptable = true; 7 this.q = []; 8 } 9 10 getFile(url, postProcess) { 11 if (!this.acceptable) 12 throw new Error('submitting queue has already been closed'); 13 const task = { postProcess: postProcess }; 14 this.q.push(task); 15 fakeAjax(url, result => { 16 task.result = result; 17 this.doPostProcess(); 18 }); 19 } 20 21 doPostProcess() { 22 while (this.q.length > 0 && this.q[0].hasOwnProperty('result')) { 23 const task = this.q.shift(); 24 task.postProcess(task.result); 25 if (!this.acceptable && this.q.length === 0) 26 console.log('all done!'); 27 } 28 } 29 30 close() { 31 this.acceptable = false; 32 this.doPostProcess(); 33 } 34} 35 36const ctx = new AsyncContext(); 37 38[ 39 "1.txt", 40 "2.txt", 41 "3.txt", 42 "4.txt", 43 "5.txt" 44].forEach(url => ctx.getFile(url, console.log)); 45ctx.close();
質問のコードテンプレートだとgetFileが必要とするコンテキストを関数の外側に用意しなければならないためそれは断念しコンテキスト保持のためのオブジェクトを用いました。
なお起動する非同期処理は決め打ちにもかかわらず後処理だけctx.getFileの引数にしている点は中途半端かつ機能分離がイケテナイ気がしました。このようなものを書くならfakeAjaxと機能を明確に分離すべきかも知れません。
投稿2017/11/19 22:34
編集2017/11/20 00:31総合スコア18392
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2017/11/20 00:29 編集
0
一切ライブラリのない、純粋なJavaScriptでは実装不可能です。
「JavaScript+言語標準で存在するオブジェクト」では、通信機能もタイマー機能もありません。
投稿2017/11/19 13:30
総合スコア145121
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/11/20 02:49 編集
0
非同期でうけとって、与えた順にソートしなおしてから表示すればいいのでは?
javascript
1var a=["1.txt","2.txt","3.txt","4.txt","5.txt"]; 2var b=[]; 3for(var i=0;i<a.length;i++){ 4 getData(i,a[i]); 5}; 6var timerId=setInterval(function(){ 7 if(b.length==a.length){ 8 clearInterval(timerId); 9 var c=b.sort(function(x,y){return x[0]>y[0];}); 10 for(var i=0;i<c.length;i++){ 11 console.log(c[i][1]); 12 } 13 console.log("all.done"); 14 } 15},100); 16function getData(num,url){ 17 var xhr = new XMLHttpRequest(); 18 xhr.open( "GET", url ); 19 xhr.onreadystatechange=function(){ 20 if(( xhr.readyState == 4 ) && ( xhr.status == 200 )){ 21 b.push([num,xhr.response]); 22 } 23 } 24 xhr.send( '' ); 25}
追記
※遅延を発生させるため、読み込むファイルはphpを指定してあります
javascript
1var a=["1.php","2.php","3.php","4.php","5.php"]; 2var b=a.map(function(){return null}); 3for(var i=0;i<a.length;i++){ 4 getData(i,a[i]); 5}; 6var pointer=0; 7var counter=0; 8var timerId=setInterval(function(){ 9 while(b[pointer]!==null){ 10 console.log(b[pointer]); 11 pointer++; 12 if(pointer>=b.length){ 13 console.log("all.done!"); 14 clearInterval(timerId); 15 break; 16 } 17 } 18 counter++; 19 if(counter>100){ 20 console.log("stop!"); 21 clearInterval(timerId); 22 } 23},100); 24function getData(num,url){ 25 var xhr = new XMLHttpRequest(); 26 xhr.open( "GET", url ); 27 xhr.onreadystatechange=function(){ 28 if(( xhr.readyState == 4 ) && ( xhr.status == 200 )){ 29 b[num]=xhr.response; 30 } 31 } 32 xhr.send( '' ); 33}
- 1.php
PHP
1<?PHP 2echo 1; 3?>
- 2.php
PHP
1<?PHP 2sleep(3); 3echo 2; 4?>
- 3.php
PHP
1<?PHP 2sleep(2); 3echo 3; 4?>
- 4.php
PHP
1<?PHP 2sleep(4); 3echo 4; 4?>
- 5.php
PHP
1<?PHP 2echo 5; 3?>
投稿2017/11/20 01:13
編集2017/11/21 05:27総合スコア114572
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2017/11/20 14:07
2017/11/20 14:16
退会済みユーザー
2017/11/20 14:34
2017/11/21 05:31 編集
0
1
JavaScript
1 2const fakeAjax = (fileName, callback) => { 3 const MAX = 5000; 4 const MIN = 1000; 5 const delay = Math.floor(Math.random() * (MAX - MIN) + MIN); 6 7 const responseMockData = { 8 "1.txt": "1st request", 9 "2.txt": "2nd request", 10 "3.txt": "3rd request", 11 "4.txt": "4th request", 12 "5.txt": "5th request" 13 }; 14 setTimeout(() => { 15 callback(responseMockData[fileName]); 16 }, delay); 17} 18 19const output = console.log.bind(this); 20 21const getFile = (fileName) => { 22 fakeAjax(fileName, (response) => { 23 handleResponse(fileName, response); 24 }); 25} 26 27const STATUS = Object.freeze({ 28 WAITING: Symbol('waiting'), 29 RECEIVED: Symbol('received'), 30 RENDERED: Symbol('rendered') 31}); 32 33 34const responseItems = new Map([ 35 ["1.txt", { status: STATUS.WAITING, content: ''}], 36 ["2.txt", { status: STATUS.WAITING, content: ''}], 37 ["3.txt", { status: STATUS.WAITING, content: ''}], 38 ["4.txt", { status: STATUS.WAITING, content: ''}], 39 ["5.txt", { status: STATUS.WAITING, content: ''}] 40]); 41 42const handleResponse = (fileName, response) => { 43 44 if (responseItems.get(fileName).status === STATUS.WAITING) { 45 responseItems.get(fileName).status = STATUS.RECEIVED; 46 responseItems.get(fileName).content = response; 47 } 48 49 for (const key of responseItems.keys()) { 50 51 if (responseItems.get(key).status === STATUS.RECEIVED) { 52 output(responseItems.get(key).content); 53 responseItems.get(key).status = STATUS.RENDERED; 54 } else if(responseItems.get(key).status === STATUS.RENDERED) { 55 continue; 56 } else { 57 return; 58 } 59 60 }; 61 62 console.log('all done!') 63 64}; 65 66 67getFile("1.txt"); 68getFile("2.txt"); 69getFile("3.txt"); 70getFile("4.txt"); 71getFile("5.txt"); 72 73
#2
アニメーションGIF
const fakeAjax = (fileName, callback) => { const MAX = 5000; const MIN = 1000; const delay = Math.floor(Math.random() * (MAX - MIN) + MIN); const responseMockData = { "1.txt": "1st request", "2.txt": "2nd request", "3.txt": "3rd request", "4.txt": "4th request", "5.txt": "5th request" }; setTimeout(() => { callback(responseMockData[fileName]); }, delay); } const output = console.log.bind(this); const getFile = (fileName) => { let memoizedResult; let memoizedCallback; fakeAjax(fileName, (response) => { if (memoizedCallback) { return memoizedCallback(response); } memoizedResult = response; }); return (callback) => { if (memoizedResult) { return callback(memoizedResult); } memoizedCallback = callback; }; } const getFile1 = getFile('1.txt'); const getFile2 = getFile('2.txt'); const getFile3 = getFile('3.txt'); const getFile4 = getFile('4.txt'); const getFile5 = getFile('5.txt'); getFile1(resultForFile1 => { output(resultForFile1); getFile2(resultForFile2 => { output(resultForFile2); getFile3(resultForFile3 => { output(resultForFile3); getFile4(resultForFile4 => { output(resultForFile4); getFile5(resultForFile5 => { output(resultForFile5); output('all done!'); }) }) }) }) })
投稿2017/11/19 14:53
編集2017/11/23 10:24総合スコア2415
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2017/11/20 00:29 編集
0
ゴチャゴチャしたコードになってしまいましたが...。
lang
1const urls = ["1.txt", "2.txt", "3.txt", "4.txt", "5.txt"] 2const buffers = urls.map(url => { 3 const buffer = {done: false, result: undefined, onComplete: undefined} 4 fakeAjax(url, result => { 5 buffer.done = true 6 buffer.result = result 7 if (buffer.onComplete) buffer.onComplete(result) 8 }) 9 return buffer 10}) 11 12function waitFor(index, onAllComplete) { 13 function printAndNext(val) { 14 console.log(val) 15 if (index < urls.length - 1) { 16 waitFor(index + 1, onAllComplete) 17 } else { 18 if (onAllComplete) onAllComplete() 19 } 20 } 21 const bf = buffers[index] 22 if (bf.done) { 23 printAndNext(bf.result) 24 return 25 } 26 bf.onComplete = result => { 27 printAndNext(result) 28 } 29} 30 31waitFor(0, () => { 32 console.log("all done!") 33})
投稿2017/11/19 12:25
総合スコア2551
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2017/11/19 12:44
0
試しに書いてみました。
(() => { const URLs = ['1.json', '2.json', '3.json', '4.json', '5.json']; const ajax = (url) => { var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.send(''); return xhr; }; const proc = (responses) => { return () => { const flag = responses.every((response) => { return response.readyState === 4 && response.status === 200; }); if (!flag) { setTimeout(proc(responses)); } else { console.log('success'); responses.forEach((response) => { console.log(response.responseText); }); } }; }; const responses = URLs .map((url) => { return ajax(url); }); proc(responses)(); })();
投稿2017/11/20 03:34
退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
CoffeeScriptで書いてみました。
CoffeeScript
1# ajaxのフェイクバージョンのつもり 2fakeAjax = (url, callback) -> 3 # レスポンスまでのランダムなタイムラグをシミュレート 4 time = Math.floor(Math.random() * (3000 - 1000) + 1000) 5 responseMock = 6 '1.txt': '1st request' 7 '2.txt': '2nd request' 8 '3.txt': '3rd request' 9 '4.txt': '4th request' 10 '5.txt': '5th request' 11 setTimeout -> 12 callback(responseMock[url]) 13 , time 14 15getFile = (url) -> 16 new Promise (resolve) -> 17 fakeAjax url, (response) -> 18 resolve(response) 19 20[ 21 getFile('1.txt') 22 getFile('2.txt') 23 getFile('3.txt') 24 getFile('4.txt') 25 getFile('5.txt') 26 Promise.resolve('all done!') 27].reduce (pre, curr) -> 28 pre.then (v) -> 29 console.log(v) 30 curr 31.then console.log
レギュレーション違反「Promiseの使用」により失格!
Promiseが禁止なら、擬きを作れば良い。
CoffeeScript
1# 約束擬き 2class PseudoPromise 3 constructor: (executor) -> 4 executor(@resolve) 5 6 then: (func) -> 7 new PseudoPromise (resolve) => 8 @fulifillment = (value) -> 9 result = func(value) 10 if result instanceof PseudoPromise 11 result.then (resultValue) -> 12 resolve(resultValue) 13 else 14 resolve(result) 15 @fulfill() 16 17 resolve: (value) => 18 @value = value 19 @fulfill() 20 21 fulfill: () -> 22 if @value? and @fulifillment? 23 @fulifillment(@value) 24 25 @resolve = (value) -> 26 new PseudoPromise (resolve) -> 27 resolve(value) 28 29# ajaxのフェイクバージョンのつもり 30fakeAjax = (url, callback) -> 31 # console.log "start: #{url}" 32 # レスポンスまでのランダムなタイムラグをシミュレート 33 time = Math.floor(Math.random() * (3000 - 1000) + 1000) 34 responseMock = 35 '1.txt': '1st request' 36 '2.txt': '2nd request' 37 '3.txt': '3rd request' 38 '4.txt': '4th request' 39 '5.txt': '5th request' 40 setTimeout -> 41 # console.log "end: #{url}" 42 callback(responseMock[url]) 43 , time 44 45getFile = (url) -> 46 new PseudoPromise (resolve) -> 47 fakeAjax url, (response) -> 48 resolve(response) 49 50[ 51 getFile('1.txt') 52 getFile('2.txt') 53 getFile('3.txt') 54 getFile('4.txt') 55 getFile('5.txt') 56 PseudoPromise.resolve('all done!') 57].reduce (pre, curr) -> 58 pre.then (v) -> 59 console.log(v) 60 curr 61.then console.log
とりあえずこのコードで動くための実装にしているため、かなり横着している部分があります(失敗する場合とか)。PromiseのPolyfillを頭に付けた方が良いと思いますが、CoffeeScriptの実装が見当たらなかったので、仕方が無く、それっぽいのを作りました。
審査委員A「これは、Promiseの使用にあたるのでは無いかね?」
審査委員B「後半のコードなど、先日の失格コードそのままでは無いか!これでは新しいコードとは言わん。」
審査委員C「それに、このPromise実装は不十分なところが多すぎる。実用的とはとても言えん。」
私「PseudoPromiseはPromiseじゃないよ、たとえPromiseだとしても、Promiseと言う名のクラスだよ!」
審査委員長「判定を言い渡す。ただのPromiseのパクリであり、創意工夫が見られないため失格とする。以上。」
(良い方法が思いついたら)PureScriptかOpalあたりで出直してきます・・・
投稿2017/11/19 12:14
編集2017/11/20 13:31総合スコア21733
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/11/19 12:16 編集
退会済みユーザー
2017/11/19 12:17
2017/11/20 13:19
2017/11/20 13:21
退会済みユーザー
2017/11/20 14:09
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。