非同期のコールバック関数に外から値を渡す
コールバック関数のパラメータ以外のものを外から渡す方法について。
ここでは setTimeout()
の第1引数に渡すコールバック関数を例にします。
単に主題の件をやりたければ、コールバック関数のスコープ外で宣言した変数に渡したい値をセットしておいて、それをコールバック関数の内側から参照させればいいです。
例として次のコードを実行すると、1000ミリ秒後に alert
で『(^-^)』が表示されます。この場合、str
がコールバック関数のスコープ外の変数にあたります。
const str = '(^-^)';
setTimeout(() => {
alert(str);
}, 1000);
ただしこの方法で参照される str
の値は、実際にコールバック関数が実行された時点における str
の値となります。
それを確認できるのが次の例です。次のコードを実行すると、1000ミリ秒後に alert()
で表示されるのは『(^-^)』ではなく『(>_<)』です。
let str = '(^-^)';
setTimeout(() => {
alert(str); // 実際にこれが実行される時点での str は '(>_<)'
}, 1000);
str = '(>_<)';
setTimeout()
はnミリ秒後に行う処理を予約する関数です。setTimeout()
で alert()
を予約した後、1000ミリ秒後までに str
の中身が変化してるので、このようになります。
もし「コールバック関数を仕掛けたタイミングにおける変数の値」を実行時にも使用したい場合は、次のように書きます。次のコードを実行すると、1000ミリ秒後も alert
で『(^-^)』が表示されます。
let str = '(^-^)';
setTimeout(createCallback(str), 1000);
str = '(>_<)';
function createCallback(arg) {
return () => {
alert(arg);
}
}
これは、「『alert()
を行う無名関数』を生成して返却する別の関数 (ここでは createCallback()
)」を間に挟んでる形です。
JavaScriptの引数は、Object型ではない限り値渡し = 『値がコピーされた別の変数』扱いです。createCallback()
で生成した無名関数が参照している arg
は、あくまで createCallback()
が実行されたときに渡された str
のコピーなので、こういうことができます。
余談
なんでこんな記事を書いたのかというと、初心者の頃にループ内で非同期のコールバック関数に外からループカウンタを渡して、想定通りに動かなかったことがあるからです。
例として次のコードを実行すると、1000ミリ秒後に alert()
で 3
が3回表示されます。実際に setTimeout()
で仕掛けたコールバックが実行されるタイミングでは、すでに i
の値がループを走り抜けて 3
に変化しているためです。
// 変数宣言が var なのは当時の再現です
for (var i = 0; i < 3; ++i) {
setTimeout(() => {
alert(i); // 実行時には i = 3
}, 1000);
}
なお、このケースに限っては var
の代わりに let
を使うだけで解決します。let
ならばループごとに別スコープ扱いになるので、同じように書いても1000ミリ秒後に alert()
で 0
-> 1
-> 2
と表示されます。
for (let i = 0; i < 3; ++i) {
setTimeout(() => {
alert(i); // 実行時には i = 0 -> 1 -> 2
}, 1000);
}