Promiseを返す関数とは

JavaScript

よく非同期関数とかプロミスが返ってくる、とか言いますが、結局なんなのか。そもそも

非同期関数 と Promiseを返してくれる関数 は違うもの。

ベン図で言うと以下のような感じです。

時間がかかるかかからないかも関係なく、Promiseを返す意思があるかどうかが重要です。Promiseを返そうと思えばPromiseは返ります。

つまり、Promiseが返るように設計しなければPromiseは返ってきません。Promiseが返ってくるということは、誰かがPromiseを返すようにしてくれているということです。

非同期と同期関数

promiseが返ってくるということは非同期関数ということですが、同期関数と非同期関数の違いはなんでしょうか。

同期関数・・・終わるまでは他の処理がブロックされる

非同期関数・・・終わらなくても他の処理をブロックしない

処理に時間がかかるかどうかは関係ないです。

同期関数

function longRunningProcess() {
    // 長時間かかる処理
    for (let i = 0; i < 1e9; i++) {}  
    return '完了';
}

const result = longRunningProcess();
console.log(result);  
console.log("短い処理");  // '完了' が表示されるまで待つ

例えば以上のようなコード、同期関数ですが、一番目のconsole.logに表示されるのに少し時間がかかります。そして二番目の軽いconsole.logの処理は一番目の処理が終わるまで実行されません。他の処理が重い処理が終わるまでブロックされてしまっています。

非同期関数

setTimeout(() => {
    console.log('遅延処理');
}, 1000); 

console.log('終了'); //終了 が表示された後に 遅延処理 が表示される

コールバック関数は非同期処理の代表的なものですが、1秒かかります。ですが他の処理は一秒待つことなく、即実行され、一秒経ってからsetTimeoutのコールバック関数が実行されます。

他の処理をブロックするかしないかが違いです。時間がかかる処理ばかり挙げられますが、時間がかかる処理でないとブロックしてるかどうかがわかりにくいからなだけです。

Promiseを返すか返さないか

Promiseを返す関数は自動的に非同期関数になります。というか非同期な処理をしたいからPromiseを返すようにするんだと思います。非同期関数ということで他の処理をブロックせずに時間のかかる処理を行う、そしてさらにPromiseを返す。なぜPromiseを返すのか。以下が主なメリットです。

Promiseを返したい動機

1. 時間がかかる非同期処理を扱う(時間がかからないこともあるが、大抵時間がかかる)

2. 処理の結果を知る

3. 処理の成功や失敗に応じた後続処理を行う

1の非同期に処理が行われるのはPromiseを返さなくても非同期関数なら同じように処理できます。問題は2,3です。非同期処理が失敗した場合、全体のコードが止まることはなくても、何らかの予期しない動作が発生する場合があります。処理の結果を適切に扱えるメリットがあります。また、処理が成功したとき、失敗したときの、後の処理を行うことができます。特にfetchなどでapiを叩いたあとなど、その返ってきた結果を表示したり処理したいことが多く、Promiseが返ってくることがとても重要です。

どうやったらPromiseを返せる?

基本的には二つの方法があります。

1.Promiseインスタンスを作成する

2.async 関数を使う

各手法を見てみます。

1.Promiseインスタンスを作成する

function exampleWithPromise() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const success = true;
            if (success) {
                resolve('成功!');  // 成功時の結果をresolveで返す
            } else {
                reject('失敗!');  // 失敗時のエラーをrejectで返す
            }
        }, 1000);  // 1秒後に結果を返す
    });
}

exampleWithPromise()
    .then(result => console.log(result))  // '成功!' が出力される
    .catch(error => console.log(error));  // エラーがあれば '失敗!' が出力される

ちなみにここではasync awaitを使えばtry catchも使えます(try catchはエラーハンドリングに有用な構文なので別に非同期関数に限って使用するものではありません。then catchはPromiseの結果のハンドリングにのみ使われます)

try catchでハンドリングした例

async function executeAsyncTask() {
    try {
        const result = await exampleWithPromise();
        console.log(result); // 成功した場合の結果を表示
    } catch (error) {
        console.error(error); // 失敗した場合のエラーメッセージを表示
    }
}

executeAsyncTask();

2.async 関数を使う

async function exampleWithAsync() {
    return '成功!';  // これがPromiseで返される
}

exampleWithAsync()
    .then(result => console.log(result))  // '成功!' が出力される
    .catch(error => console.log(error));  // エラーがあれば処理

普通の関数にasyncを付けるだけでPromiseを返す関数になります。returnで返された値が自動的にresolveされたPromiseの値になります。

awaitを使えば、他の非同期処理を待つこともできます。(awaitはPromiseを返す非同期関数にしか使えない

async function exampleWithAsyncAndAwait() {
    const result = await new Promise((resolve) => {
        setTimeout(() => resolve('非同期処理完了!'), 1000);
    });
    return result;
}

exampleWithAsyncAndAwait()
    .then(result => console.log(result))  // '非同期処理完了!' が出力される
    .catch(error => console.log(error));
async function exampleWithFetch() {
    // fetch API を使った非同期処理
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
}

// async 関数を呼び出して、その結果を then と catch で処理
exampleWithFetch()
    .then(data => console.log(data))
    .catch(error => console.error(error));

例えば上記のようにfetchAPIしたときも、fetch関数がPromiseを返してくれています。Promiseが返ってくるとき、誰かがPromiseを返してくれています。Promiseは思いやりでできているんですね。Promiseを返すことのできるエンジニアになりたいものです。