k8o

お知らせ

関数の同期・非同期を気にせず処理するPromise.tryとは

公開: 2025年3月8日(土)
更新: 2025年3月8日(土)
閲覧数47 views
JavaScriptBaseline 2025PromisePromise.try

はじめに

2025年のBaselineにPromise.tryが追加されました。Promise.tryは同期的な関数と非同期的な関数を区別せずに手続きを進めさせるメソッドです。

GitHub - tc39/proposal-promise-try: ECMAScript Proposal, specs, and reference implementation for Promise.try

ECMAScript Proposal, specs, and reference implementation for Promise.try - tc39/proposal-promise-try

github.com

この記事ではPromise.tryの使い方と、有効な場面について解説します。

Promise.tryとは

Promise.tryは引数に与えた関数の同期・非同期に関わらず、結果をPromiseに包んでから返します。

Promise.try(callback)
  .then(() => console.log('fulfilled'))
  .catch(() => console.log('rejected'))
  .finally(() => console.log('settled'));

Promiseで包まれた結果が渡ってくるので、非同期に書かれたasyncFnだけではなく、syncFnの結果もthenを通して取り出せます。

const syncFn = () => {
  return 'sync';
};
 
const asyncFn = async () => {
  return 'async';
};
 
console.log(Promise.try(syncFn)); // Promise {<fulfilled>: 'sync'}
console.log(Promise.try(asyncFn)); // Promise {<pending>}
 
Promise.try(syncFn).then(console.log); // sync
Promise.try(asyncFn).then(console.log); // async

例外の処理もtry...catchを利用するのではなく、catchを通して行います。

const syncFn = () => {
  throw 'sync';
};
 
const asyncFn = async () => {
  throw 'async';
};
 
console.log(Promise.try(syncFn)); // Promise {<rejected>: 'sync'}
console.log(Promise.try(asyncFn)); // Promise {<pending>}
 
Promise.try(syncFn).catch(console.log); // sync
Promise.try(asyncFn).catch(console.log); // async

これまでもnew PromisePromiseを生成することで同様の挙動を実現できましたが、今後はPromise.tryを使うだけで表現可能なので便利になりました。

const try = (callback, ...args) => {
  return new Promise((resolve) => callback(...args));
};

Promise.tryで呼び出す関数に引数を渡す方法は、第2引数以降に渡す方法と関数を新たらしく作る2つの方法があります。

const fn = (arg1, arg2) => {
  return arg1 + arg2;
};
 
Promise.try(fn, 1, 2).then(console.log); // 3
Promise.try(() => fn(1, 2)).then(console.log); // 3

Promise.tryが有効な場面

Promise.tryは同期的な関数と非同期的な関数が入り乱れたものを取り扱う時に便利です。 以下の例を考えてみましょう。

const cache = new Map();
 
const readFileWithCache = (path): string => {
  if (cache.has(path)) {
    return cache.get(path);
  }
 
  return fs.readFile(path, 'utf8').then((data) => {
    cache.set(path, data);
    return data;
  });
};

上記のreadFileWithCache関数はpathに存在するファイルを読み込む関数です。 初回の読み込みはfs.readFileを用いて非同期に値を取り出しますが、2回目以降はcacheに保存された値を返します。

この関数を実行してみると、1度目の読み込みはPromise<Buffer>が返されますが、それ以降はBufferを返巣ようになっています。キャッシュしてくれるのは嬉しいですが、取り出した値を利用する視点ではとても不便です。

console.log(readFileWithCache('path/to/file')); // Promise {<pending>} or Buffer

このような関数もPromise.tryを使えば初回とそれ以降の動作を意識せずに活用できます。

console.log(Promise.try(() => readFileWithCache('path/to/file'))); // Promise {<pending>}

キャッシュされた結果を返す時も、Promise.tryで実行することでPromise<Buffer>を返すようになります。 この例ではPromise.tryを使う以外でも使いやすい形に改良可能ではありますが、Promise.tryの威力が伝わったかと思います。