こんにちは、白々さじきです。
今回は、コールバック形式のAPIをPromiseでラップして、ReactのuseEffectで使う方法についてまとめます。
Geolocation APIで現在地を取得しようとしたとき、return しても値が取れなくてハマったので、備忘録も兼ねて書きます。(私の学習のために生成AIに作らせた文章を一部加筆修正しました。)
結論
コールバック形式のAPIは Promise でラップすると await で使えるようになります。Reactでは await はコンポーネントのトップレベルで使えないため、useEffect の中で async 関数を定義して呼びます。
コールバック形式のAPIとは
まず、今回の詰まりポイントを整理します。
Geolocation APIの getCurrentPosition はこういう形の関数です。
navigator.geolocation.getCurrentPosition(
(position) => { /* 成功したときに呼ばれる */ },
(error) => { /* 失敗したときに呼ばれる */ }
);
処理が終わったあとに「コールバック関数」を呼ぶ仕組みなので、return で値を外に返せません。
function getPosition() {
navigator.geolocation.getCurrentPosition((position) => {
return position.coords; // ← ここで return しても外には届かない
});
}
const pos = getPosition(); // undefined になる
最初、なぜ undefined になるのか分かりませんでした。コールバックの中の return は、外側の関数の戻り値にはならないんですよね。
Promise でラップする
コールバック形式のAPIを await で使えるようにするには、Promise でラップします。
Promise は「将来の成功または失敗を表すオブジェクト」です。resolve(値) を呼ぶと成功、reject(エラー) を呼ぶと失敗になります。
function getCurrentPosition(): Promise<{ latitude: number; longitude: number }> {
if (!navigator.geolocation) {
return Promise.reject(new Error('Geolocation is not supported'));
}
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
(position) => {
// 成功したら resolve で値を渡す
resolve({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
});
},
() => {
// 失敗したら reject でエラーを渡す
reject(new Error('位置情報の取得に失敗しました'));
},
);
});
}
これで await getCurrentPosition() と書けるようになります。
コンポーネントのトップレベルで await は使えない
await は async 関数の中でしか使えません。Reactのコンポーネント関数は通常の関数なので、直接 await を書くとエラーになります。
// NG: コンポーネントのトップレベルで await は使えない
const MyComponent: FC = () => {
const result = await getCurrentPosition(); // エラー
};
useEffect の中で async 関数を定義する
Reactで非同期処理を書くときは、useEffect の中で async 関数を定義して呼ぶのが定番のパターンです。
useEffect 自体を async にするのはNGです。useEffect のコールバックはクリーンアップ関数を返す必要があり、async にすると Promise が返ってしまいます。
// NG: useEffect 自体を async にしてはいけない
useEffect(async () => {
const result = await getCurrentPosition();
}, []);
// OK: useEffect の中で async 関数を定義して呼ぶ
useEffect(() => {
async function fetchPosition(): Promise<void> {
const result = await getCurrentPosition();
setPosition({ lat: result.latitude, lng: result.longitude });
}
fetchPosition();
}, []);
実際のコード(現在地を取得して表示する例)
const LocationDisplay: FC = () => {
// 初期値を null にする(まだ取得できていない状態を表現)
const [position, setPosition] = useState<{ lat: number; lng: number } | null>(null);
useEffect(() => {
async function fetchPosition(): Promise<void> {
const result = await getCurrentPosition();
setPosition({ lat: result.latitude, lng: result.longitude });
}
fetchPosition();
}, []);
if (position === null) {
return <p>現在地を取得中...</p>;
}
return (
<p>
緯度: {position.lat} / 経度: {position.lng}
</p>
);
};
null 初期値と ?? の組み合わせ
useState の初期値を { lat: 0, lng: 0 } にしてしまうと、?? でのフォールバックが効かなくなります。
// NG: 初期値がオブジェクトだと ?? が効かない
const [position, setPosition] = useState({ lat: 0, lng: 0 });
const display = position ?? fallback; // 常に position が使われる(null にならないため)
// OK: null を初期値にする
const [position, setPosition] = useState<{ lat: number; lng: number } | null>(null);
const display = position ?? fallback; // position が null のとき fallback が使われる
?? は左辺が null または undefined のときだけ右辺を使います。{ lat: 0, lng: 0 } は「値がある」と判断されるため、初期値は null にしておくのがポイントです。
まとめ
- コールバック形式のAPIを
awaitで使うにはPromiseでラップする resolve(値)で成功、reject(エラー)で失敗を表現する- コンポーネントのトップレベルで
awaitは使えない useEffectの中でasync関数を定義して呼ぶのが定番パターン- 「まだ取得できていない」状態は
null初期値で表現し、??でフォールバックする
コールバック形式のAPIをPromiseでラップするのは最初とまどいましたが、仕組みを理解してからは自然に書けるようになりました。同じところで詰まっている方の参考になれば幸いです。
参考リンク
サポートのお願い
下記リンクからお買い物いただけると、ブログ運営のための費用が増え、有料サービスを利用した記事作成が可能になります。ご協力よろしくお願いします!

コメント