5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JavaScriptのPromiseを理解する

Posted at

Promiseが導入される前は、コールバック関数を使って非同期処理を制御していました。
しかし、コールバック関数をつなげた場合、コードの階層が深くなり、可読性が下がってしまうという問題がありました。

そこで考えられたのがPromiseです。

Promiseを使うことでより直感的に非同期処理を記述できるようになります。

Promiseとは

非同期処理をより簡単に、可読性が上がるようにした記述方法です。

Promiseは、以下の3つのステータスを持っています。

  • Pending:初期の状態、または処理待ちの状態
  • Fullfilled:処理が成功して完了した状態
  • Rejected:処理が失敗した状態

Promiseオブジェクトのステータスによって、処理を分岐していきます。

Promiseの構文

new Promise((resolve,reject) => {
  // 同期処理
  resolve() // もしくは reject()
})
  .then(() => {
    // 非同期処理(resolveを待つ)
  })
  .catch(() => {
    // 非同期処理(rejectを待つ)
  })
  .finally(() => {
    // 非同期処理(then、またはcatchを待つ)
  });

Promiseの基本形です。new PromiseでPromiseをインスタンス化します。インスタンス化したPromiseのthenメソッド、catchメソッド、finallyメソッドを使って、非同期処理に対して制御を加えていきます。

Promise構文の中では、new Promiseの引数に与えたコールバック関数は同期処理されますが、thenメソッドやcatchメソッド、finallyメソッドは全て非同期で実行されます。

Promiseの使い方(resolve, then)

new Promise((resolve, reject) => {
  resolve('hello');
})
  .then((data) => {
    console.log(data); // hello
  })
  .catch(() => {
    // スキップされる
  })
  .finally(() => {
    console.log('end'); // end
  });

Promiseの引数として、コールバック関数を設定します。コールバック関数はresolverejectの2つの引数を取ります。
resolveが呼ばれた場合は、thenメソッドの中のコールバック関数が実行され、このコールバック関数の引数にはresolveで渡した実引数が渡ってきます。

thenメソッドが完了すると、catchメソッドはスキップされ、finallyメソッドに処理が移ります。finallyメソッドでは終了処理を記述します。

Promiseの使い方(reject, catch)

new Promise((resolve, reject) => {
  reject('bye');
})
  .then(() => {
    // スキップされる
  })
  .catch((data) => {
    console.log(data); // bye
  })
  .finally(() => {
    console.log('end'); // end
  });

rejectは、Promiseのコールバックでエラーが発生したときにPromiseに通知するために使用する関数です。
rejectが呼ばれた場合、catchメソッドの中のコールバック関数が実行され、引数にはrefectで渡した実引数が渡ってきます。

catchメソッドの中のコールバック関数が実行された後に、finallyメソッドに処理が移ります。

Promiseの使い方(finally)

finallyメソッドに渡したコールバック関数は必ず実行されるため、共通の終了処理を記述します。
また、finallyメソッドの中で使用するコールバック関数は引数を使用することができません。

Promiseの流れを確認する

ここからは、Promiseの流れを確認していきます。

new Promise((resolve, reject) => {
  console.log('promise');
  resolve();
}).then(() => {
  console.log('then');
});

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. then

globalthenよりも先に表示されます。thenメソッドに渡したコールバック関数は非同期処理なため、コールスタックが空になった後に実行されます。

thenを繋げてみる

new Promise((resolve, reject) => {
  console.log('promise');
  resolve();
})
  .then(() => {
    console.log('then');
  })
  .then(() => {
    console.log('then');
  })
  .then(() => {
    console.log('then');
  })
  .then(() => {
    console.log('then');
  });

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. then
// 4. then
// 5. then
// 6. then

thenを繋げると、thenメソッドに渡されたコールバック関数が順番に処理されます。
コールバック関数を使った非同期処理と比べて、thenを繋げても多階層になることはありません。

catchメソッドを追加してみる

new Promise((resolve, reject) => {
  console.log('promise');
  reject(); // resolve → rejectに変更
})
  .then(() => {
    console.log('then');
  })
  .catch(() => {
    console.log('catch');
  });

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. catch

resolverejectに変更すると、thenメソッドはスキップされcatchメソッドに処理が移ります。
catchメソッドも非同期処理されるため、コンソールの一番最後にcatchが表示されます。

thenの中でcatchに処理を移行したい場合

rejectnew Promiseの引数に渡すコールバック関数内でしか使えません。
そのため、thenメソッドの中でcatchメソッドに処理を移行したい場合には、throwを使います。

new Promise((resolve, reject) => {
  console.log('promise');
  resolve();
})
  .then(() => {
    console.log('then');
    throw new Error();
  })
  .then(() => {
    console.log('then');
  })
  .catch(() => {
    console.log('catch');
  });

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. then
// 4. catch

1回目のthenは実行されますが、2回目のthenは実行されません。
throwでエラーが発生したことをPromiseに伝えることでcatchメソッドに処理が移行します。

finallyを追加してみる

finallyは、resolveの場合はthenメソッドの後、rejectの場合はcatchメソッドの後に必ず呼ばれます。

new Promise((resolve, reject) => {
  console.log('promise');
  resolve();
})
  .then(() => {
    console.log('then');
  })
  .catch(() => {
    console.log('catch');
  })
  .finally(() => {
    console.log('finally');
  });

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. then
// 4. finally

thenメソッド(catchメソッド)のコールバック関数に引数を設定する場合

new Promise((resolve, reject) => {
  console.log('promise');
  resolve('hello');
})
  .then((data) => {
    console.log(`then: ${data}`);
    return data;
  })
  .then((data) => {
    console.log(`then: ${data}`);
    return data;
  })
  .catch((data) => {
    console.log(`catch: ${data}`);
  })
  .finally((data) => {
    console.log(`finally: ${data}`);
  });

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. then: hello
// 4. then: hello
// 5. filally: undefined

1つ目のthenメソッドのdataに値を渡すためにはroselveに引数を渡す必要があります。この例で言うと、helloがthenメソッドの引数として設定されます。

2つ目のthenメソッドのdataに値を渡すためには1つ目のthenメソッドの中でreturnを使用します。
ただし、finallyは引数を取れないのでthenメソッドの中でretrunを使用してもundefindとなります。

上記同様、rejectが呼ばれた場合はcatchメソッドに処理が移り、dataで値を受け取れます。

new Promise((resolve, reject) => {
  console.log('promise');
  reject('bye');
})
  .then((data) => {
    console.log(`then: ${data}`);
    return data;
  })
  .then((data) => {
    console.log(`then: ${data}`);
    return data;
  })
  .catch((data) => {
    console.log(`catch: ${data}`);
  })
  .finally((data) => {
    console.log('finally');
  });

console.log('global');

// コンソール
// 1. promise
// 2. global
// 3. catch: bye
// 4. finally

PromiseにsetTimeout(非同期関数)を使ってみる

Promoseを使うとresolverejectが呼ばれるまではthenやcatchが呼ばれません。
そのため、Promiseのコールバック関数に非同期関数を組み込むことで非同期をうまく制御することができます。

new Promise((resolve, reject) => {
  console.log('promise');
  resolve('hello');
})
  .then((data) => {
    console.log(`then: ${data}`);
    return data;
  })
  .then((data) => {
    console.log(`then: ${data}`);
    return data;
  })
  .catch((data) => {
    console.log(`catch: ${data}`);
  })
  .finally((data) => {
    console.log(`finally: ${data}`);
  });

console.log('global');

// コンソール
// 1. promise
// 2. global

// ...1秒後 

// 3. then: hello
// 4. then: hello
// 5. filally: undefined

1秒間経った後にresolveが呼ばれるので、後続の処理はresolveが呼ばれた1秒後に実行されます。
resolverejectに変えた場合も同様です。

Promiseはこのようにして非同期処理を実行することができます。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?