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

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

ただいまの
回答率

90.33%

  • JavaScript

    17542questions

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

  • 非同期処理

    115questions

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

  • 同期

    31questions

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

JavaScriptでの再帰関数を含んだ時の挙動

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 242
退会済みユーザー

退会済みユーザー

質問

JavaScriptで
・ディレクトリ内のファイルを列挙し、配列化する再帰関数xがあり、
・その配列をログに表示する関数yがあります
これを逐次的に処理させたいのでpromiseなどを自学していますが、再帰処理が含まれる場合どうすればよいかわかりません。

わからないところ

1.どの関数に、どうやってpromiseを使えばよいかわからない
2.そもそもpromiseがあまり理解できない
3.学習用に再帰のない関数でpromiseを使って試した時はできた。だが再帰処理になるとわからなくなる

3に関して、なぜ再帰だとわからないのか、というと、学習時はreturn new Promiseという形で、promiseをリターンしていたので、再帰の場合どう扱われるか、途端にわからなくなります

該当コード

<div id="dropzone">
    <button class="btn btn-primary btn-block" id="cover" data-toggle="collapse" data-target="#collapseSample1">
        ここにファイルを
        <br>
        ドロップしてください
    </button>
</div>
#dropzone {
    text-align: center;
    width: 1000px;
    height: 70px;
    margin: 10px;
    padding: 10px;
    border-radius: 10px;
            }

#cover{
    position: absolute;
    margin: 200px 400px;
    width: 1000px;
    height: 70px;
            }


このコードは、ブラウザからドラッグ&ドロップでディレクトリを取得した時に動くコード

document.getElementById("dropzone")
.addEventListener("dragover", function (event) {
    event.preventDefault();
}, false);

document.getElementById("dropzone")
.addEventListener("drop", function (event) {
    let items = event.dataTransfer.items;
    let results = [];
    event.preventDefault();
    for (let i = 0; i < items.length; i++) {
        let item = items[i].webkitGetAsEntry();
        scanFiles(item, results);
    }
    setTimeout(() => viewLog(results), 500);
}


再帰関数x、この処理をし、results(再帰関数xにはtmpObjectで値が渡っている)にしっかり値が入った後に、関数yを実行したい

function scanFiles(entry, tmpObject) {
    switch (true) {
        case (entry.isDirectory) :
            let entryReader = entry.createReader();
            entryReader.readEntries(function (entries) {
            for (let i = 0, len = entries.length; i < len; i++)
                scanFiles(entries[i], tmpObject);
            });
            break;
        case (entry.isFile) :
            tmpObject.push({"fileName": entry.name, "fullPath": entry.fullPath});
            break;
        default :
            break;
    }
}


関数y

function viewLog(results) {
    console.log(results);
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • mather

    2018/05/25 18:37 編集

    逐次処理とは手順通りに順番に処理していくことを意味します。並列で動作するスクリプトに「逐次処理」と書くのは変な用語の使い方ですよ。つまり「逐次的に処理させたいのでpromiseなどを自学しています」とありますがpromiseは要らないことになってしまいます。

    キャンセル

  • 退会済みユーザー

    退会済みユーザー

    2018/05/25 18:52

    申し訳ございません。同期処理と言えばよいでしょうか?Promiseを使わず実行すると、再帰関数xが終わる前にすべての関数が終了してしまい、再帰関数xで作られる配列が扱えないので、どうにか扱えるようにしたいです。setTimeoutで擬似的に処理していますが、取得したディレクトリの大きさがわからないのでsetTimeoutを使うと正しい処理がされない時があるのです。

    キャンセル

  • mather

    2018/05/25 18:56

    提示されたコードを見る限り、scanFilesの処理が非同期で実行されているように見えないので、なにか省略されたコードがありませんか?

    キャンセル

  • 退会済みユーザー

    退会済みユーザー

    2018/05/25 18:58

    省略しているコードは、HTML側の表示と、一番上で提示したコードを実行する時の判定のみです。この判定はHTML側で指定した範囲にディレクトリがドラッグ&ドロップされた時に実行するように書いてあります。本来のコードに修正しておきます。

    キャンセル

回答 1

checkベストアンサー

+2

1.どの関数に、どうやってpromiseを使えばよいかわからない

自分でPromiseのインスタンスを作るのは、Promiseを返さないような非同期な処理をPromiseに変換するときです。今の場合entryReader.readEntries(...)がそれです。

scanFilesが処理を終えたタイミングで何かをするには、それがPromiseを返す必要があります。そしてそのPromiseはスキャンが終了した時にresolveしなければいけません。

            for (let i = 0, len = entries.length; i < len; i++)
                scanFiles(entries[i], tmpObject);
            });

この部分は複数回scanFilesが呼ばれ、それがすべてresolveするまで待つ必要があるので、Promise.allか何かを使うといいです。

async/awaitを使うと楽です。
async/awaitもpromiseのsyntactic sugarですので、まずpromiseを理解することをおすすめします。

JSFiddle Demo

window.addEventListener("dragover", e => {
  e.preventDefault()
})

window.addEventListener("drop", async e => {
  e.preventDefault()
  const items = e.dataTransfer.items
  const results = []
  for (const item of items) {
    const entry = item.webkitGetAsEntry()
    await scanFiles(entry, results)
  }
  console.log(results)
})

async function scanFiles(entry, tmpObject) {
  if (entry.isDirectory) {
    const entryReader = entry.createReader()
    const entries = await new Promise((resolve, reject) => {
      entryReader.readEntries(
        entries => resolve(entries),
        err => reject(err)
      )
    })
    await Promise.all(entries.map(entry => scanFiles(entry, tmpObject)))
  } else if (entry.isFile) {
    tmpObject.push({
      "fileName": entry.name,
      "fullPath": entry.fullPath
    })
  }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/26 00:49

    動作は僕が求めてる通りに動いてくれたので、promiseとasync/awaitをしっかり理解でき次第ベストアンサーにさせていただきます
    本当にありがとうございました

    キャンセル

  • 2018/05/26 01:26

    わからない部分があれば質問してください(といってもasync/awaitを一からすべて説明できないですが)。

    キャンセル

  • 2018/05/26 10:19

    何から何までありがとうございます
    scanFilesのif(isDirectory)の中の処理を追っていくのが難しいのですが、
    entriesにPromiseをいれて、その後のPromise.allで、代入されたPromiseの本来の値を取得し、isFileの時else分岐に飛んで中身が作られる、ということであっていますでしょうか?

    キャンセル

  • 2018/05/26 14:02 編集

    > entriesにPromiseをいれて

    いえ、await してるので、entries には promise の resolve した値が入ります。

    > その後のPromise.allで、代入されたPromiseの本来の値を取得し

    いえ、entries は Array ですから map で各要素を scanFiles します。scanFiles は Promise を返すので、この map の返り値は Promise の Array になります。そして Promise.all に Promise の Array を渡すと、その返り値はまた Promise で、それはArray 内のすべての Promise が resolve したとき resolve します。この Promise を await することにより、すべての scanFiles が完了するのを待つということになります。

    キャンセル

  • 2018/05/26 14:29

    promiseのresolveした値が、最終的に欲しい値で、promiseは、後々そこに最終的な値が入るよ、という約束が入っている
    という認識でよろしいでしょうか、、恥ずかしながら未だにpromiseを理解できてなくて、、

    キャンセル

  • 2018/05/26 14:56

    > promiseのresolveした値が、最終的に欲しい値

    そうです。今の場合 scanFiles が返す Promise の resolve した値は使ってないですが。

    > promiseは、後々そこに最終的な値が入るよ、という約束が入っている

    いえ、Promise は resolve または reject によって完了したとみなされます。処理が失敗したことを表すのに reject が使われます。なので当然、そのような約束はありません。resolve するかもしれないし、reject するかもしれないし、永久に何も起こらないかもしれません。例えば
    new Promise(() => {})
    とすれば永久に resolve も reject もしないです。

    言われて気づきましたが entryReader.readEntries はエラーが発生しうるので reject をちゃんとしたほうがよかったですね。
    https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryReader/readEntries
    回答を編集しておきます。

    キャンセル

  • 2018/05/26 15:01

    ありがとうございます。最後までしっかり教えていただけたおかげで、理解が深まりました。
    自分の実現したい事もできるようになりました。本当に助かりました。ありがとうございました。

    キャンセル

同じタグがついた質問を見る

  • JavaScript

    17542questions

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

  • 非同期処理

    115questions

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

  • 同期

    31questions

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