LoginSignup
takahasinaoki
@takahasinaoki (なおき)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

JavaScript でファイルを開くダイアログを出す方法

解決したいこと

JavaScriptの初心者です。

JavaScript でファイルを開くダイアログを出して、ファイルを読ませたいのですが、
参考にした記事で疑問があったので質問します。

参考
JavaScript でファイル保存・開くダイアログを出して読み書きするまとめ

const showOpenFileDialog = () => {
    return new Promise(resolve => {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.txt, text/plain';
        input.onchange = event => { resolve(event.target.files[0]); };
        input.click();
    });
};

const readAsText = file => {
    return new Promise(resolve => {
        const reader = new FileReader();
        reader.readAsText(file);
        reader.onload = () => { resolve(reader.result); };
    });
};

(async () => {
    const file = await showOpenFileDialog();
    const content = await readAsText(file);
    // 内容表示
    alert(content);
})();

上記のコードで、ファイルを開くダイアログでキャンセルをクリックした場合、どうなるのでしょうか?
const file = await showOpenFileDialog();
が永遠に待機し続けるような気がするのですが、コードは終了するのですか?
コードが終了しない場合でも特に問題はないのでしょうか?

自分で試したこと

vscodeで
const file = await showOpenFileDialog();
の次の行にブレークポイントを設定してデバッグしてみましたが、ファイルを選択したときはブレークしましたが、キャンセルしたときはブレークしませんでした。

0

2Answer

永遠に待機し続けるような気がするのですが、

ご認識の通りです。Promiseは resolve もしくは reject が呼ばれるまでずっと待機します。ダイアログを開いては閉じることを繰り返すと、 await の箇所で止まったまま永遠に待ち続ける Promise がどんどん溜まっていきます1。問題があるかないかで言うと特にないでしょう(めちゃくちゃたくさん繰り返すとリソースが足りなくなったりはするかもしれません1)。

このことをはっきりさせるため、 showOpenFileDialog() を次のように書き換えてみます。

const showOpenFileDialog = () => {
    return new Promise((resolve, reject) => { // rejectも追加
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.txt, text/plain';
        input.onchange = event => { resolve(event.target.files[0]); };
        input.click();
        setTimeout(() => { reject("timeout!"); }, 5000); // 5秒後にrejectする
    });
};

5秒以内にファイルを選択すれば変更前と同じ処理が実行されます。先に resolve が実行された時点で Promise は終了してしまうため、後から reject が呼ばれても何も起きません。

一方5秒を過ぎてしまうと reject が実行され、呼び出し元でエラーが発生します(これは try-catch で捕まえることができます)。この場合もここで Promise は終了してしまうため、後から resolve が呼ばれても何も起きません。

参考になりましたら幸いです。


  1. 参照元がなくなってしまうので内部的に破棄されたりするかもしれません。ここは私の知識ではなんとも言えません。 

1

Comments

  1. @takahasinaoki

    Questioner
    丁寧な解説ありがとうございました。

    >問題があるかないかで言うと特にないでしょう(めちゃくちゃたくさん繰り返すとリソースが足りなくなったりはするかもしれません1)。

    特に問題がないということで安心しました。

試しに以下のコードで実行したところ
ファイルを開くダイアログが表示された時点で
1 3 5 6 7 2 がlog出力されたため
const file = await showOpenFileDialog();
の終了を待たずにmain関数(goClick)が終了することが確認できました。
キャンセルを選択したところ、後続のlogは出力されませんでした。
ファイルを選択したところ結果は
1 3 5 6 7 2 8 4となりました

<button onclick="goClick()">go</button>
<script>
const showOpenFileDialog = () => {
    return new Promise(resolve => {
        console.log(5);
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.txt, text/plain';
        input.onchange = event => { 
            console.log(8);
            resolve(event.target.files[0]); 
        };
        console.log(6);
        input.click();
        console.log(7);
    });
};

function goClick()
{
    console.log(1);
    goClick2();
    console.log(2);
}
async function goClick2()
{
    console.log(3);
    const file = await showOpenFileDialog();
    console.log(4);
}
</script>

0

Your answer might help someone💌