

現在indexedDBを利用したアプリケーションを作成中で、カーソルを利用して取得したオブジェクトを前もって宣言した配列に順次追加していくコードを以下のように書いたのですが、思ったように動いてくれません。

[code]

    function myFunc(){
    //中略
    var columns = [];
    index.openCursor(range).onsuccess = function (event) {
        if(event.target.result == null) {
            return;
        }
        var cursor = event.target.result;
        columns.push(cursor.value);
        cursor.continue();
    };
    return columns;
    }
    
[/code]

このcolumnsの値をコンソールに出力すると、初期状態では`[]`という表示のみがあり、その括弧を選択すると

[code]

    Array[0]
    0:Object
    1:Object
    
[/code]

という表示になります。また、それぞれの要素には期待した通りの値が入っています。  
しかし、この配列は扱い上空の配列となっているようで、`columns.toString()`の返り値を先程の`return
columns;`の直前で出力すると空の文字列が返ってきてしまいます。

ちなみに同様に`columns.toString()`の返り値を`if(event.target.result == null)
{}`の内部にて出力すると、出力は`[object Object]`となりました。

自分なりに調べていくうちに、恐らく配列のスコープというものがこの問題に関連しているのではないかということだけはなんとなく理解出来てきたのですが、どうすればこれを回避できるかには思い当たりませんでした。

実際のアプリケーション上ではこのcolumnsをさらに加工して利用したいのですが、`columns[0]`が`undefined`となるためループを利用することも出来ず、`columns.filter()`といった処理も期待通りに実行されません。

なぜこのような現象が起こってしまうのか、どのように改めれば回避が可能かなどご教示いただければ幸いです。


CC BY-SA 3.0
2016-12-07T16:44:36.390
30993
2016-12-08T02:41:07.737
8841
question
javascript
関数外で宣言し、関数内で要素をpushした配列が空の配列としてみなされる


スコープの問題ではなく、非同期処理の問題ですね。あなたが`onsuccess`プロパティに代入しているクロージャー`function (event)
{...}`は、すぐに実行されるのではなく、`index.openCursor(range)`で作成したrequestが成功裏に完了した
**後で実行される** ようにrequestに登録しただだけで、登録はすぐに完了し、そのクロージャーが呼ばれないうちに`return
columns;`が走ります。つまりその時点では`columns`は空なので、あなたの`myFunc()`関数の戻り値は空配列になると言うわけです。(その後でrequestが実行される。)

indexedDBが使えるような最近のブラウザなら`Promise`を使えよと怒られそうですが、ここでは古典的な完了用のコールバックを使うパターンを示しておきます。

あなたの`myFunc`は次のように書き換えることになります。

[code]

    function myFunc(onComplete) {
        //中略
        var columns = [];
        index.openCursor(range).onsuccess = function (event) {
            if(event.target.result == null) {
                //最後までデータを読み込んだら完了用のコールバックを呼ぶ
                onComplete(columns);
                return;
            }
            var cursor = event.target.result;
            columns.push(cursor.value);
            cursor.continue();
        };
        //この行に制御が移った時、まだonsuccessに渡したクロージャーは実行されていない
        //return columns;
    }
    
[/code]

呼び出す時は、

[code]

    var cols = myFunc();
    //colsを使った処理...
    
[/code]

のように関数の戻り値として結果を取得するのではなく、次のように完了用のクロージャーを引数として渡し、そのクロージャー内で結果を受け取る形になります。

[code]

    //完了用のコールバックには引数を１個取るクロージャー(関数でも良い)を渡す
    myFunc(function(cols) {
        //colsを使った処理...
    });
    
[/code]

データベースを開いて、オブジェクトを定義して、テスト用のデータを流し込んで…と言った諸々の準備を全部想像して書く時間はなかったので、小さなミスがあるかも知れませんが、基本的にはこの考え方でデータを取得してそれを利用するコードが書けるはずです。お試しください。


CC BY-SA 3.0
2016-12-07T17:38:18.090
30996
2016-12-07T17:38:18.090
13972
30993
answer


@Le Pere d'OO様のご助言により、無事に（一応は）期待通りの挙動をするものをpromiseを用いて書くことが出来ました。  
まだ自分の中ではpromiseに対して「非同期処理が終わるのを待ってから実行してくれるようにするためのもの」という程度の認識しかないため、利用法として望ましい書き方になっているかは分かりませんが、一応回答という形で残させていただきます。

[code]

    function myFunc(){
        //中略
        return new Promise(function (resolve,reject){
            var columns = [];
            index.openCursor(range).onsuccess = function (event){
                if(event.target.result == null) {
                    resolve(columns);
                    return;
                }
                var cursor = event.target.result;
                columns.push(cursor.value);
                cursor.continue();
            };
        });
    }
    
[/code]

というように`myFunc`を改め、これを呼び出す際に

[code]

    myFunc().then(function (columns) {
        //「columns」を使った処理をここに書く
        console.log(columns.toString());    //[object Object](空ではない)
    })
    
[/code]

とすることで無事にオブジェクトが格納された配列を取得することが出来ました。  
ひとまずはこれでちゃんと動くものが書けましたが、何か誤りや「こうすべきである」という点等ございましたらご指摘お願い致します。


CC BY-SA 3.0
2016-12-08T02:41:07.737
31005
2016-12-08T02:41:07.737
8841
30993
answer