LoginSignup
4
5

More than 5 years have passed since last update.

AngularJS で Promise (defer)を使ってファイルシステムからファイルを読み込む (cordova filesystem)

Posted at

Monaca & OnsenUI でハイブリッドアプリ

OnsenUI = AngularJSなので、MonacaでAngularJSの形に従いながら、アプリケーションを作成している。

端末にファイルを保存&読み取りする場合、PhoneGap(Cordova) FileAPI のお世話になる。

HTML5 File API でファイルを扱う

ファイルを読み書きするだけならコールバック関数に仕事してもらって・・で良いんだけど、ファイルの中身をリスト形式で持たせて、画面に表示・・を使用とすると、表示したいタイミングでデータが取得出来てるわけではない非同期処理の壁に阻まれた。。

Promise でやってみる

そんな時はPromise!

AngularJS で Promise するには、$q.defer() する。

基本パターン

function slowCall() {
  var df = $q.defer();
  // 非同期処理を担当する関数を定義
  function fn() {
    var msg = 'now : ' + new Date().getTime();
    alert('inside async function.');
    df.resolve(msg);
  };
  $timeout(fn, 1000);
  return df.promise;
};

この、定義した slowCall 関数はPromiseを返してくれる。
非同期処理した後に、その結果を用いて更に処理を続けたい時は・・

呼び出し側

var testDeferSample = function () {
  alert('test defer sample start.');
  slowCall().then(function () {
    alert('END!');
  });
  alert('Opps.. outside');
};

呼び出すとこんな感じ。

test defer sample start.
Opps.. outside
inside async function.
END!

非同期処理が無事終わった後のEND!

幾つも非同期呼び出しして、最後の最後になんか処理をはさみたい時

$q.all を使う。

var testAllSample = function() {
  alert('test sample start.');
  var p1 = slowCall().then(function(msg) {
    alert('LAST 1' + msg);
    return msg;
  });
  var p2 = slowCall().then(function(msg) {
    alert('LAST 2' + msg);
    return msg;
  });
  $q.all([p1,p2]).then(function(res) {
    alert('LAST MOST ' + res[0] + ', ' + res[1]);
  });
};

呼び出すとこんな感じ

test all sample start.
inside async function.
LAST 1 1440643852941
inside async function.
LAST 2 1440643876515
LAST MOST 1440643852941, 1440643876515

p1, p2で返してきた値が最後のall呼び出しで受け取れる。

p1, p2で何も return しない場合、 undefined が返ってくる。
resはallで呼び出した関数の数分のオブジェクトが入っている。

ファイル操作の実際

ファイルシステムを取得

var getFileSystem = function() {
  var deferred = $q.defer();
  window.requestFileSystem(window.PERSISTENT, 1024*1024, function(filesystem) {
    deferred.resolve(filesystem);
  });
  return deferred.promise;
};

パスを指定してディレクトリを取得

var getTargetDirectory = function(dirPath) {
  var deferred = $q.defer();
  getFileSystem().then(function(filesystem) {
    filesystem.root.getDirectory(dirPath, { create: true, exclusive: false },
      function(dirEntry) {
        deferred.resolve(dirEntry);
      },
      failFiles
    );
  });
  return deferred.promise;
};

ディレクトリをオープンする時に、指定されたディレクトリがない場合、作成するオプションを渡している。
{ create: true, exclusive: false }
作成したくない場合、 { create: false } にする。

failFiles はディレクトリの取得に失敗した場合を扱うエラー処理関数。

こんな感じでエラーがあったことを表示してるだけ。

function failFiles(error) {
  alert('failFiles' + error.code);
};

指定されたディレクトリにあるファイル名を一覧で返す

var listDir = function (dirPath) {
  var deferred = $q.defer();
  getTargetDirectory(dirPath).then(function (dirEntry) {
    var reader = dirEntry.createReader();
    var fileNames = [];
    reader.readEntries(
      function (entries) {
        for (var i = 0; i < entries.length; i++) {
          fileNames.push(entries[i].name);
        }
        deferred.resolve(fileNames);
      },
      function (error) {
        console.log("reading target directory error");
      }
    );
  });
  return deferred.promise;
};

ファイルの中身を読む場合

var readFile = function(dirPath, fileName) {
  var deferred = $q.defer();
  getTargetDirectory(dirPath).then(function(dirEntry) {
    dirEntry.getFile(fileName, { create: false },
      function(fileEntry) {
        fileEntry.file(function(file) {
          var reader = new FileReader();
          reader.onloadend = function() {
            deferred.resolve(this.result);
          };
          reader.readAsText(file);
        });
      },
      function(error) {
        console.log("reading target directory error");
      }
    );
  });
  return deferred.promise;
};

ファイルを保存する場合

var _writeFile = function(dirPath, fileName, fileText) {
  var deferred = $q.defer();
  getTargetDirectory(dirPath).then(function(dirEntry) {
    dirEntry.getFile(fileName, { create: true },
      function(fileEntry) {
        fileEntry.createWriter( 
          function(writer) {
            var cb = function() {
              //console.log("write end");
              alert("保存しました。");
              deferred.resolve(fileName);
            }
            writer.onwrite = cb;
            writer.onerror = function() { console.log("保存に失敗しました。"); }
            //console.log( fileText );
            writer.write( fileText );
          } ,
          function() {
            console.log("create writer error");
          }
        );
      },
      failFiles
    );
  });
  return deferred.promise;
};

ディレクトリを指定したら、ファイル名とファイルの中身をJSONにして返して欲しい

ディレクトリパスとファイル名を指定されたら、JSONオブジェクトにして返すPromise.

var createJsonObject = function (targetDirPath, fileName) {
  var deferred = $q.defer();
  readFile(targetDirPath, fileName).then(function (content) {
    var json = { name: fileName, data: content };
    deferred.resolve(json);
  });
  return deferred.promise;
};

後でJSONオブジェクトを$q.all()呼び出しするために、関数を生成する関数をリストで保持する

var createCallbackFunctionList = function (targetDirPath, fileNames) {
  var deferred = $q.defer();
  var functions = [];
  fileNames.map(function (fileName) {
    functions.push(createJsonObject(targetDirPath, fileName));
  });
  deferred.resolve(functions);
  return deferred.promise;
};

本体

前提:ファイル名を保存時に new Date().getTime() で作ってる。
ゴール:ファイル名の降順に並び替えしてデータを取得

var getFileAsJsonList = function () {
  var jsonList = [];
  var targetDirPath = 'YOUR_TARGET/DIRECTORY/HERE';

  var deferred = $q.defer();
  // まずディレクトリにあるファイル名を元に、JSONオブジェクトを取ってくる関数を作る
  FileUtilService.listDir(targetDirPath).then(function (fileNames) {
    createCallbackFunctionList(targetDirPath, fileNames).then(function (fns) {
      deferred.resolve(fns);
    })
  });

  // ファイルコンテンツ取得時に非同期処理のため、ファイルの順序がバラバラになる
  // 全ファイルコンテンツの取得後に、ファイル名(日時)で並べ替え。
  deferred.promise.then(function (promises) {
    // 全部実行したい関数の配列を取得 then の後にくる関数は、
    // promisesが全て実行し終わったら一番最後に実行される
    $q.all(promises).then(function (photos) {
      jsonList = photos.sort(function (a, b) {
        return parseInt(b.name) - parseInt(a.name);
      });
      alert('got' + jsonList.length);
      angular.forEach(jsonList, function (value, key) {
        console.log('FileInfo : ' + value.name + ', content length=' + value.data.length);
      })
    });
  });
};

参考資料

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5