echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Javascript】非同期処理の待ち時間にアニメーションを表示する

概要

Webやスマホアプリで、何か非同期処理を行っている際に画面に表示される、くるくる回ったりするアニメーション(正式にはなんていうの...Xamarin.Formsの「Activity Indicator」です)を表示する方法です。

f:id:Makky12:20190517200801g:plain

作成方法

まず「画像はどうするの」となりますが、これは「Loader Generator」というサイトで、簡単に作成&ダウンロードできます。
※サイズ・回転速度・色など、いろいろ細かいところまで調整でき、便利です。

実際に表示する方法

これは、実際にサンプルを見た方が早いと思いますので、ソースを。
(ソース自体は、そこまで難しい内容ではないので、説明は省略)

/* ------------------------------
 非同期処理を実行する関数
 ------------------------------ */
function asyncFunc() {  

    // Loading 画像を表示
    dispLoading("処理中...");
   
    // 非同期処理(例)
    $.ajax({
      async : true,
      url : "http://xxx.com/yyy/zzz",
      type:"GET",
      dataType:"json"
    })
    // 通信成功時
    .done(function(data) {
      alert("成功しました");
    })
    // 通信失敗時
    .fail( function(data) {
      alert("失敗しました");
    })
    // 処理終了時
    .always( function(data) {
      // Loading 画像を消す
      removeLoading();
    });  
}  
    
/* ------------------------------
 表示用の関数
 ------------------------------ */
function dispLoading(msg){
  // 引数なしの場合、メッセージは非表示。
  if(msg === undefined ) msg = "";
  
  // 画面表示メッセージを埋め込み
  var innerMsg = "<div id='innerMsg'>" + msg + "</div>";  
  
  // ローディング画像が非表示かどうかチェックし、非表示の場合のみ出力。
  if($("#nowLoading").length == 0){
    $("body").append("<div id='nowLoading'>" + innerMsg + "</div>");
  }
}
 
/* ------------------------------
 表示ストップ用の関数
 ------------------------------ */
function removeLoading(){
  $("#nowLoading").remove();
}  
#nowLoading {
  display: table;
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  background-color: #fff;
  opacity: 0.8;
}
 
#innerMsg {
  display: table-cell;
  text-align: center;
  vertical-align: middle;
  padding-top: 140px;
  z-index:100;
  background: url("表示するくるくる画像のURL]") center center no-repeat;
}

ポイント

  • 非同期処理開始したら「#NowLoading」及び「#innerMsg」のタグを埋め込み、終了したら削除する。
  • 「#NowLoading」のタグは、body直下の子要素にする。
  • z-indexは、モーダルなどを表示する場合は、大きい値にしておく。
    • モーダルのz-indexとの兼ね合いで非表示になることがある。
  • 「#NowLoading」のタグは、もちろんHTMLに記載してもOK。
    • その場合、visibility:visible(hidden)などで制御する。

と、また簡単になりましたが、今回はこの辺で。

【Javascript】非同期処理のPromiseやasync/awaitについて その2

前回はPromiseについて書きましたが、その続きで今回はasync/awaitについて。

【参考】
async/await 入門(JavaScript)

asyncとは

非同期関数を定義する関数宣言のこと。
async関数は、以下の動作をする。

  • Promiseを返す
  • 値をreturnすると、Promiseはそれをresolveする。
  • エラーなどをスローすると、Promiseはそれをrejectする。
  • 関数内部でawaitを使用できる。
// 普通こんな書き方しませんが、サンプルということで。  
function main() {  
    
    const even = checkEven(2);
  
    // この場合、コンソールには「偶数です」と表示される。
    even.then((message) => {
        console.log(message);
    }).catch((err) => {
        console.log(err.message);
    });
  
    const odd = checkEven(1);
  
    // この場合、コンソールには「奇数です!」と表示される。
    odd.then((message) => {
        console.log('偶数です');
    }).catch((err) => {
        console.log(err.message);
    });
}
  
async function checkEven(num) {
    if(num % 2 === 0) {
        // resolve('偶数です')と同じ。
        return '偶数です';
    } else {
        // reject('奇数です!')と同じ。
        throw new Error('奇数です!');
    }
}

awaitとは

  • Promiseを返す関数について、Promiseの結果(resolveやreject)が返されるまで、処理を待つ。
    • 同期処理みたいな動作になる。
  • awaitを付けた場合、戻り値に格納されるのはresolve、あるいはrejectされた値となる。
  • rejectされたのがエラー場合、そのエラーが発生する。(catch()処理などを実施する必要あり)
  • async関数内でのみ使用できる。

先程の関数を、awaitを使って書くと、こうなります。

async function main() {  
    
    // この場合、messageにはresolve値の「偶数です」が格納されるので、  
    // それがそのまま表示される。
    try {
        const even = await checkEven(2);
        console.log(even);
    } catch(err) {
        console.log(err.message);
    }
  
    // この場合、checkEvenからErrorがスローされるので、コンソールには  
    // err.message(「奇数です!」)が表示される。
    try {
        const odd = await checkEven(1);
        console.log(odd);
    } catch(err) {
        console.log(err.message);
    }
}
  
async function checkEven(num) {
    if(num % 2 === 0) {
        // resolve('偶数です')と同じ。
        return '偶数です';
    } else {
        // reject('奇数です!')と同じ。
        throw new Error('奇数です!');
    }
}

await使用時の注意点

  • awaitは基本的に「resolveの値がないと、それ以降の処理ができない」際に使用する。(下記など)
    • ファイルの内容を取得する
    • DBのあるテーブルのレコードを取得する
  • awaitは先述の通り「Promiseの結果が返されるまで、処理を待つ」動作。
    • async関数だからと言って、なんでもawaitすればいいわけではない。
    • 使い方を間違えると、レスポンス時間の遅延につながる。(下記ソース)
async function main() {  
    
    // funcVal1~funcVal3が「それぞれ処理に5秒かかる」場合、  
    // awaitで直列処理してしまうと、main()が終わるのに15秒かかってしまう。  
    try {
        const val1 = await funcVal1();
        const val2 = await funcVal2();
        const val3 = await funcVal3();
    } catch(err) {
    }
  
    // この場合、await は使わず、Promise.all()を使って並列処理すれば  
    // main()が終わるのは5秒で済む。
    const func1 = funcVal1();
    const func2 = funcVal2();
    const func3 = funcVal3();
  
    Promise.all([func1, func2, func3]).then(([val1, val2, val3]) => {
        // すべて正常終了した際の処理
    }).catch((err) => {
        // どれか一つでもErrorがスローされた際の処理
    });
}

また急ぎ足気味になってしまいましたが、今日はこの辺で。

【Javascript】非同期処理のPromiseやasync/awaitについて その1

Promiseについて

Promiseとは、

  • 非同期処理を制御するためのしくみ
  • ES2015以降で使用可能

メリット

その1. コードが簡潔になり、見やすくなる。(個人差あり)

例えば、非同期関数func1〜func3があったとして、下記の制約がある場合、

  • func2はfunc1の結果が必要
  • func3はfunc2の結果が必要

一般的なコールバック関数を使用した場合、ソースは下記のようになる。
(典型的な「コールバック地獄」というやつです)
※しかもエラー処理を省略してるので、エラー処理も書いたらもっと煩雑になる...

function main() {  
    func1((err, data) => {
        func2(data, (err2, data2) => {
            func3(data2, (err3, data3) => {
                console.log(data3);
            });
        });
    });
}

これがPromiseを使えば、例えばこうなります。

// resolveの引数は、正常終了した際の戻り値。  
// rejectの引数は、エラー発生した際の戻り値。

function funcPromise1() {
    return new Promise(function(resolve, reject) {
        func1((err, data) => {
            if(!err) {
                resolve(data);
            } else {
                reject(err);
            }
        });
    });
}  

function funcPromise2(data) {
    return new Promise(function(resolve, reject) {
        func2(data, (err, data2) => {
            if(!err) {
                resolve(data2);
            } else {
                reject(err);
            }
        });
    });
}  

function funcPromise3(data2) {
    return new Promise(function(resolve, reject) {
        func3(data2, (err, data3) => {
            if(!err) {
                resolve(data3);
            } else {
                reject(err);
            }
        });
    });
}  

function main() {  
    funcPromise1.then((data) => {return funcPromise2(data);})  
        // resolveされた場合、then()の引数にはresolveの引数が格納される。  
        // rejectされた場合、catch()の引数にはrejectの引数が格納される。
        .then((data2) => { return funcPromise3(data2); })
        .then((data3) => { console.log(data3); })
        .catch((err) => { console.log(err); });
}

funcPromise1~funcPromise3の関数定義は増えましたが、ソース自体は見やすくなったと思います。

エラー処理が簡潔になる

(先程のソースで若干ネタバレしてますが)func1〜func3のいずれかでエラーが発生しても、Promiseなら最後の「catch(err)」でまとめてエラー処理ができます。

これがコールバック関数の場合、こうなります。
(クッソ見にくい...というか、try~catchを使った場合、全体を囲ってtry~catchではエラー捕捉ができない(=毎回外側にthrowしなくちゃいけない)ので、もっと大変なことに。)

function main() {  
    func1((err, data) => {
        if(!err) {
            func2(data, (err2, data2) => {
                if(!err2) {
                    func3(data2, (err3, data3) => {
                        if(!err3) {
                            console.log(data3);
                        } else {
                            console.log(err3);
                        }
                    });
                } else {
                    console.log(err2);
                }
            });
        } else {
            console.log(err2);
        }
    });
}

非同期処理の並行実行の制御ができる

Promiseを使った場合、例えば複数の非同期処理を同時実行した場合に、下記のような処理が可能です。

    
function main() {  
    // Promise.race()は、引数の非同期処理のうち、
    // いずれか1個の処理が完了したら次へ進む
    Promise.race([funcPromise, funcPromise2, funcPromise3])
         // 最初に終わった処理がresolveの場合、thenの引数dataに
         // その処理のresolve値が格納される。
         // また最初に終わった処理がrejectの場合、catchの引数errに
         // その処理のreject値が格納される。
        .then((data) => { console.log(data); })
        .catch((err) => { console.log(err); });
  
    // Promise.all()は、引数の非同期処理がすべて終了するまで次へ進まない。
    Promise.all([funcPromise, funcPromise2, funcPromise3])
        // 引数data~data3には、それぞれfuncPromise~funcPromise3の
        // resolve値が格納される。(すべてresolveしないとthen()は実行されない)
        .then(([data, data2, data3]) => { 
            console.log(data); 
            console.log(data2);
            console.log(data3);
        })
        // いずれか1つでもrejectされた場合、最初にrejectされた値の
        // reject値が格納される。
        .catch((err) => { console.log(err); });
}

そして、話はasync/awaitにつながる訳ですが、それはまた後日。

【TypeScript】TypeScriptのインストールと各種環境設定

現在仕事でnode.jsを使用しており、「勉強も兼ねてTypeScriptを導入しよう!」と思い導入したので、その際のメモ。1

前提

参考ページ

packege.jsonの作成

  1. ターミナルソフト(「コマンドプロンプト」とか)を起動。
  2. package.jsonを作成したいフォルダ(アプリケーションのルートフォルダなど)にカレントフォルダを移動。
  3. 下記コマンドを実行。
> npm init
  • ここでは「npm init」の詳細は割愛。
  • 質問事項については、名前以外は「yes」にしておいてOK。(後で変更可能)

TypeScriptのインストール

  • ターミナルソフトで、インストール先フォルダをカレントフォルダにする。(package.jsonと同じで良いなら不要)
  • 下記コマンドを実行。
> npm install --g typescript

テストソースの作成

カレントフォルダに「test.ts」というファイルを作成し、下記ソースを書く。

class Student {
    fullName: string;
    constructor(public firstName: string, public middleInitial: string, public lastName: string) {
        this.fullName = firstName + " " + middleInitial + " " + lastName;
        console.log(this.fullName);
    }
}
  
let student = new Student('Leon', 'Scott', 'Kenedy');

その後、ターミナルソフトで下記コマンドを入力。

> tsc test.ts

問題なければ、下記の状態になるはず。

  • ターミナルにカレントフォルダのみが表示される。
  • カレントフォルダに、test.jsファイルが作成される

あとは、test.jsをnode.jsで実行し、下記結果が表示されればOK.

> node test  
Leon Scott Kenedy

その他

ブラウザ(クライアント)側javascript作成時の注意

もちろんTypeScriptでブラウザ側のjavascriptを作成することも可能。
ただしブラウザ側は、「node.jsの「require」(=TypeScriptの「import」)が使用できないため、そのままでは外部ファイルやライブラリをできない、という問題がある。

そこで、この問題を解決する必要があるが、その1つが「browserify」というモジュール。

これは、

  • require先のjsファイルの内容も、まとめて1ファイルにまとめてしまう、というモジュール。
  • require先jsファイルがさらにrequireしている..などの場合でも、依存関係のあるjsファイルをまとめて1つにしてくれる。
  • 結果、requireが不要になるので、ブラウザ側でもrequire先のjsdファイルを使用できる。

手順としては、

  • npmコマンドでインストールして、
> npm install -g browserify
  • 統合先のjsファイルを「-o」オプションで指定して、
> browserify test.js -o bundle.js
  • 最後に、作成された統合先のjsファイルをHTMLなどに埋め込めばOK。
> <script src="bundle.js"></script>
  • 大元のjsファイル(色々requireしているファイル)を「test.js」とします。
  • 統合先ファイルに命名ルールはないですが、通例として「bundle.js」とするようです。

※もちろん、「browserify」以外にも対応策はいろいろありますので、興味があれば探してみてください。

型定義ファイルの作成

「型定義ファイル」とは、

  • 「各種モジュールをTypeScriptで使用可能にする」ためのファイル。
  • Javascriptで記載されてるモジュールを、TypeScriptに変換するためのもの。
  • 型定義ファイルを用意すると、VSCodeでインテリセンスなどが使用可能になる。
  • 型定義ファイルは、下記のコマンドでインストール可能。
> npm install -save @types/(モジュール名) 

※各モジュールの型定義ファイルのインストールコマンドは、下記サイトで検索可能です。

tsconfig.jsonの設定(2019/5/5追記)

tsconfig.jsonとは、

  • TypeScriptや、そのコンパイル時の各種設定を定義するためのファイル。
  • プロジェクトのルートフォルダ(=package.jsonと同じ場所)に保存することで、設定が有効になる。
  • tsconfig.jsonを作成するには、プロジェクトのルートフォルダで下記コマンドを実行すればOK。
> tsc --init 
定義 説明
module モジュール関連のコードをどの方式として扱うか(Common.js, umdなど)
target JavaScriptのバージョン(ES5, ES2015など)
noImplicitAny 型未定義の変数などをエラーとするか(trueならエラー、falseならany型にする。)
strict 型厳格な方チェックを行うか(trueなら実施する。)
outDir コンパイル後のjsファイルの保存先フォルダ(未指定ならtsconfig.jsonがあるフォルダ)
rootDir tsファイルの保存先のルートフォルダ(未指定ならtsconfig.jsonがあるフォルダ)
esModuleInterop common.jsモジュールとESモジュール互換的に扱えるようにするかどうか
strictNullChecks nullやundefinedのチェックを厳格にする(number型の初期値にnullを代入不可、など)
sourceMap souuceMapを作成するかどうか(TypeScriptコード上でブレークポイント設定など、デバッグ可能になる)
exclude tsファイルの保存先として除外するフォルダ(ここで指定したrootDir以下の全フォルダ内の*.tsファイルが、コンパイル対象になる)

とりあえず、今回はこの辺で。


  1. 仕事ではjavascriptを直で書いてます。

【Visual Studio Code】Settings Syncで環境を同期する

前回のブログで、最後に「環境の再現なら、Settings Syncを使えば...」という記載をしたのですが、VS Codeでの環境を同期する拡張機能「Settings Sync」が使いやすかったので、それを使用した環境の同期方法を書きました。

前提

前提として、下記が必要になります。

Githubの「個人アクセストークン」の取得方法

  1. Githubにログインして、右上のメニューから「Settings」を選択
  2. 左のメニューから「Developer Settings」を選択
  3. 左のメニューから「Personal Access Tokens」を選択する。
  4. 「Token description」には任意の文字列(「Settings Sync Token」とか)を入力し、チェックボックスは「gist」のみチェックを付ける。
  5. 1~4を終えると、トークンが表示されるので、メモなりコピーなりしておく。
    • このトークンは一度画面を閉じると、再度表示できないので注意。
    • トークンを忘れたら、「Generate New Token」で別のトークンを生成する。

f:id:Makky12:20190503195003p:plain f:id:Makky12:20190503195017p:plain f:id:Makky12:20190503195028p:plain

インストール&VSCode設定のアップロード方法

  • インストールは、VS Codeの「拡張機能」から「Settings Sync」で検索すれば見つかるので、インストールすればOK。
  • インストール完了後、コマンドパレットの「Sync:設定をアップロード」(またはshift+alt+「u」)を実行。
  • Github の個人アクセストークンを入力してください」と表示されるので、先程取得したトークンを入力する。

f:id:Makky12:20190503195509p:plain f:id:Makky12:20190503195047p:plain

「Gist ID」の確認方法

  1. アップロードを完了後、Github右上のメニューから「Your Gists」を選択
  2. 「cloudSettings」という項目があるので、選択。
  3. 「cloudSettings」のURLを見ると、「https:// gist.github.com/(ユーザー名)/(文字列)」となっており、最後の(文字列)の部分がGist IDになる。

設定のダウンロード方法

  1. コマンドパレットの「Sync:設定をダウンロード」(またはshift+alt+「d」)を実行。
  2. Github の個人アクセストークンを入力してください」と表示されるので、最初に取得したトークンを入力する。
  3. 「Gist Id を入力してください」と表示されるので、先程確認した「Gist ID」を入力する。
  4. すると、設定のダウンロードが始まる。(「出力」にダウンロード状況が表示される。)

トークンとGist IDの順番を間違えないように注意!!
※設定をダウンロードすると、元々ローカルPCにインストールした拡張機能はなくなってしまう(「マージ」ではなく「上書き」)。
なのでダウンロード前に、あらかじめアップロードしておくこと!!

f:id:Makky12:20190503195055p:plain

トークンやGist IDを間違えてしまったら?

  • コマンドパレットの「Sync:拡張機能の設定をリセット」を実行すれば、トークンやGist IDの設定が消去される。
  • その後、アップロードやダウンロードを実行すると、再度聞かれるので、正しい値を入力すればOK。

未確認事項(確認したいけどできない)

  • 複数台のPCで、それぞれ設定をアップロードすると、複数の「cloudSettings」ができる。
  • Gist側で、各*.jsonファイルの内容をマージしたら、一つに統合できるのか?
  • 下記「対処法が不明な現象」のせいで、確認ができない...

対処法が不明な現象

  • VS Codeの最新版(1.33.1)をインストール後、Settings Syncでアップロードやダウンロードを行おうとすると、下記エラーが発生する。
    • DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.
  • 「Buffer()」が脆弱性の関係で非推奨なので別の記載をしろ、というエラーだが、いろいろネットで調べても、有効な回避法が見つかっていない。
  • 対処方法をご存知の方がいれば、Twitterなどでご連絡をお願いします。

最後、尻切れトンボみたいな感じになってしまいましたが、だいたいこんな感じで、環境の同期ができます。

【Visual Studio Code】「ERRO Bad arguments:」で更新ができない場合の対策

現象

Visual Studio Codeで「更新の確認」などを行い、アップデートを行った際、

  • 下の画像のようなエラーが表示され、更新に失敗する。
  • エラーメッセージに記載されているログを確認すると、「ERRO Bad arguments: Code path doesn't seem to exist: (Code.exeの絶対パス)」というログが記載されている。

f:id:Makky12:20190426201106p:plain

【注】

  • 下の画像では消しましたが、実際には一番下に、詳細情報が記載されたログファイルの保存先が表示されています。
  • 「ERRO」は記載ミスではなく、本当にそう書いてあります。(おそらく4文字にしたので、最後の「R」が削られたのかと。)

原因

(Code.exeの絶対パス)の場所に、「Code.exe」がない。

  • インストールした際のユーザーアカウントと、実際に使用しているユーザーアカウントが違う...などの際に起こる場合がある。
  • 使用アカウントを1つに固定していれば起こらないが、1人で複数アカウントを使用していると、まれに起こる。(僕みたいに...)

対策

VS Codeのアンインストール→再インストールする。

【調べたこと】

  • 「設定」にはCode.exeの保存先パスを定義しているような場所はなかった。
  • Code.exeのショートカットを「Code.exe」という名前で(Code.exeの絶対パス)に保存しても、現象変わらず。
  • レジストリエディタ」でそれらしいパスを定義してる場所があったが、変更→再起動しても、症状は改善しなかった。
    • これについては、もう少し突っ込んで調べてもよかったかも...

※個人的には、下記の理由から、リスクを負ってまでレジストリを触るくらいなら、正直再インストールしたほうが良いと思う。

  • Visual Stuidioと違って、言っても「エディタ」なので、ダウンロード&インストール自体はすぐ終わる。
  • 環境(設定や拡張機能)自体は、Settings Syncの様な便利な拡張機能を使えば、すぐ元にに戻せる。

【Docker】ベースイメージ取得時のトラブル対応

今回もDockerネタ。
ベースイメージ取得時(docker buildコマンド実行時など)に発生するトラブルと、その原因など。
ただし今回は本当に備忘録程度に。

ベースイメージ取得時のエラー(その1)

現象

ベースイメージ取得時に下記メッセージが表示され、取得に失敗する。
request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers).

原因

DNSサーバーの自動解決がうまくいっていない。

対策

DNSサーバーを自動解決させるのではなく、自分で指定する。
Docker for Windowsの場合、[Settings] - [Network] - [DNS Server]の設定を「Fixed/8.8.8.8」にするとうまくいった。(僕の場合)

ベースイメージ取得時のエラー(その2)

現象

ベースイメージ取得時に下記メッセージが表示され、取得に失敗する。
unauthorized: authentication required

原因

  • 起動時にログインができていない。
  • メールアドレスでログインしている。

対策

  • 前者は事前に正しくログインが出来ていることを確認する。
  • 後者はメールアドレスではなく、ユーザ名でログインする。

※Docker起動時のログインはメールアドレスでもOKだが、(ベースイメージ取得時など)一部データ取得時の認証が、メールアドレスだと失敗する模様。

以上、今回は本当に備忘録程度に。