質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
canvas

HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

同期

複数のディレクトリに存在するファイルを更新した場合に、すべてのファイルにも更新が行われる事、又は、同じ記憶領域に同時にアクセスして内容の整合性が失われてしまう事をを防ぐ制御などを同期と呼びます。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Q&A

解決済

2回答

9656閲覧

JavaScriptの関数内の処理順序について

tarotarosu

総合スコア114

canvas

HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

同期

複数のディレクトリに存在するファイルを更新した場合に、すべてのファイルにも更新が行われる事、又は、同じ記憶領域に同時にアクセスして内容の整合性が失われてしまう事をを防ぐ制御などを同期と呼びます。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

3グッド

2クリップ

投稿2016/07/26 01:48

###前提・実現したいこと
Canvasを用いて画像の合成処理を行っています。
頻繁に行う処理であるため以下のように関数化しました。

###該当のソースコード

javascript

1//配列内に格納された画像を順に合成していく関数 2function synthesize_img(arr, color, shade, canvas_id) { 3 width = 580; 4 height = 1620; 5 6 var canvas = document.getElementById(canvas_id); 7 var context = canvas.getContext("2d"); 8 context.clearRect(0, 0, width, height); 9 var images = []; 10 //配列内に画像パスを格納していく 11 for(var i in arr){ 12 images[i] = new Image; 13 images[i].src = arr[i]; 14 } 15 //最後尾に画像を格納 16 var last = images.length; 17 images[last] = new Image; 18 images[last].src = color; 19 //更に最後尾に画像を格納 20 var last2 = images.length; 21 images[last2] = new Image; 22 images[last2].src = shade; 23 24 //配列内のすべての画像の読み込みが終わった時点で、画像の合成と描画を行う 25 var loadedCount = 1; 26 for(var i in images){ 27 images[i].addEventListener("load", function(){ 28 if(loadedCount == images.length){ 29 //配列の最後尾の前までを合成 30 for(var j=0; j<images.length-1; j++){ 31 context.globalCompositeOperation = "source-over"; 32 context.globalAlpha = 1.0; 33 context.drawImage(images[j], 0, 0, width, height); 34 } 35 //配列の最後尾を乗算 36 context.globalCompositeOperation = "multiply"; //IEでは対応していない 37 context.drawImage(images[images.length-1], 0, 0, width, height); 38 //必要に応じて合成 39 if(arr != SRC_ARR.back && arr != SRC_ARR.right && arr != SRC_ARR.left){ 40 var img_add = new Image; 41 img_add.src = btn_front; 42 img_add.addEventListener("load", function(){ 43 context.globalCompositeOperation = "source-over"; 44 context.globalAlpha = 1.0; 45 context.drawImage(img_add, 0, 0, width, height); 46 //処理終了確認 47 console.log("Draw Finish!"); 48 }); 49 } 50 } 51 loadedCount++; 52 }, false); 53 } 54 //処理終了確認 55 console.log("All Finish!"); 56}

JavaScriptはシングルスレッドで処理されていくため、上記のような非同期処理を含まない場合、上から順に処理が行われていくのが普通だと思います。
しかし、実行しChromeのコンソールを見てみると、「All Finish!」が先に表示され、「Draw Finish!」が後に表示されます。
この原因は一体何なのでしょうか?また、これを解決するにはどうすればよいのでしょうか?(やはり、deferredを使用することでしょうか?)

mpyw, act823, ikuwow👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

Deferred+Promiseパターンを使っても構いませんが,Promise単独で使うパターンのほうが主流ですね.前者はjQueryなどのライブラリに依存しますが,後者はECMAScript2015という最新のJavaScriptの仕様の一部となっており,Chrome,Firefox,Safari,EdgeなどのWebブラウザなら標準で使うことができます.(IEを除く)

せっかくなので,古く感じられる部分をES2015ベースの書き方になおしてみます.

JavaScript

1'use strict'; 2 3/** 4 * 画像を非同期で読み込む関数 5 * @param {string} source - 画像URL 6 * @return {Promise} Imageをthenコールバックの第1引数として渡すPromise 7 */ 8function loadImageAsync(source) 9{ 10 return new Promise((resolve, reject) => { 11 const image = new Image; 12 const onLoad = () => { 13 image.removeEventListener('load', onLoad); 14 image.removeEventListener('error', onError); 15 resolve(image); 16 }; 17 const onError = () => { 18 image.removeEventListener('load', onLoad); 19 image.removeEventListener('error', onError); 20 reject(new Error('Failed to load image: ' + source)); 21 }; 22 image.addEventListener('load', onLoad); 23 image.addEventListener('error', onError); 24 image.src = source; 25 }); 26} 27 28/** 29 * 画像を合成する関数 30 * @param {Array} imageSources - 本体画像のURLの配列 31 * @param {string} colorSource - 色付けに用いる画像のURL 32 * @param {string} shadeSource - シェーディングに用いる画像のURL 33 * @param {string} canvasId - canvas要素のid属性値 34 * @return {Promise} 次の処理に繋げるためのPromise 35 */ 36function synthesizeImage(imageSources, colorSource, shadeSource, canvasId) 37{ 38 // 幅,高さ,コンテキストを定義 39 const width = 580; 40 const height = 1620; 41 const context = document.getElementById(canvasId).getContext('2d'); 42 context.clearRect(0, 0, width, height); 43 44 // すべてを読み込み終わった後にthenコールバックを実行 45 // そしてこの関数の返り値もPromiseにし,「synthesizeImageが終わった後の処理」を外部からも書けるようにする 46 return Promise 47 .all([shadeSource, colorSource, ...imageSources].map(loadImageAsync)) 48 .then(images => { 49 // 配列を分解代入 (colorSourceとimageSourcesに対応するImageはotherImagesにまとめる) 50 const [shadeImage, ...otherImages] = images; 51 52 // otherImagesの全要素についてsource-overで描画を実行 53 for (const image of otherImages) { 54 context.globalCompositeOperation = 'source-over'; 55 context.globalAlpha = 1.0; 56 context.drawImage(image, 0, 0, width, height); 57 } 58 59 // shadeImageについてmultiplyで描画を実行 60 context.globalCompositeOperation = 'multiply'; 61 context.drawImage(shadeImage, 0, 0, width, height); 62 63 if (sources === SRC_ARR.back) return; 64 if (sources === SRC_ARR.right) return; 65 if (sources === SRC_ARR.left) return; 66 67 // 条件を満たすとき,BTN_FRONTを読み込んだ後,描画を実行し,Draw Finish!と表示する 68 // そして,次のthenコールバックをこのPromiseの後に続けるためにreturnする 69 return loadImageAsync(BTN_FRONT).then(image => { 70 context.globalCompositeOperation = 'source-over'; 71 context.globalAlpha = 1.0; 72 context.drawImage(image, 0, 0, width, height); 73 console.log('Draw Finish!'); 74 }); 75 }); 76}
  • btn_frontはグローバル変数のようなので,それと分かるようにSRC_ARR同様すべて大文字にしました.
  • 上を除き,原則的にJavaScriptではcanvas_idのようにスネークケースではなくcanvasIdのようにキャメルケースにすべて統一する文化のようです.

これを呼び出すときは,以下のように必ずエラーに対応する処理も併せて書きます.

JavaScript

1'use strict'; 2 3synthesizeImage(/* ...(適宜引数を入れる) */) 4.then(() => console.log('All Finish!!')) // 最後にここが実行される 5.catch(e => console.error(e.stack || e)); // エラー時はここが実行される

投稿2016/07/26 06:41

編集2016/07/26 08:59
mpyw

総合スコア5223

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mpyw

2016/07/26 06:46

備考: 旧石器時代のJavaScriptを書いてる各位に告ぐ、現代的なJavaScript超入門 Section5 ~ES2015文法を覚えよう(前編)~ http://qiita.com/gaogao_9/items/18b20ad9b76c9c81b5fa JavaScriptは如何にしてAsync/Awaitを獲得したのか Qiita版 http://qiita.com/gaogao_9/items/5417d01b4641357900c7 ↑で紹介しているように,Promise単独ではなくcoやGeneratorと組み合わせて使うのもOKです.async/awaitを使えるようにしてもいいですが,こちらは少しコンパイルの手間が面倒ですね…
tarotarosu

2016/07/28 09:12

コードを修正してくださりありがとうございました! おかげさまで非常に参考になりました。 自分はいまだに旧石器時代のJavaScriptを書いている原人ですので、これからはできる限り最新のJavascriptの動向に合わせてコーディングしていきたいと思います! 本当にありがとうございました_(._.)_
guest

0

images[i].addEventListener("load",とありますが、これは「ロード時に以下の関数を呼び出す」という非同期処理です。

非同期処理の終了は、setTimeoutなどを使って非同期に検知するしかありません。

投稿2016/07/26 02:02

編集2016/07/26 02:03
maisumakun

総合スコア145183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問