ほぼ日刊サービス開発日誌

React, firebase, 機械学習など

sponsored

最初にこう説明してよと思った、javascriptのPromise, async/awaitの直感的な説明

わたしの理解力はアレなので、あまりスッと入ってくる直感的な説明がなかった。1ヶ月後の自分に向けて書く。

Promiseの使い所

javascriptで、APIを叩いたり、データベースと接続して、そうやって得た値にまたホニャララして、それを改造した値をさらにホゲホゲする...ことを記述すると、ネストしまくった汚いコード、俗にいうコールバック地獄になる。

だから、Promiseオブジェクトという、「実行時間がわからない関数を入れておく箱」を人類はつくった。

その箱にはアラーム、つまり「実行がおわった」とか、「実行に失敗した」とかを、お知らせしておく機能をついてて、べんりだよ、という(⚠自分でアラームをセットしないと使えないけど...後述)。

しかも、その箱には、「実行がおわったらこれやってくれー」という命令を、ネストせずに渡すことができる、then()というチェーンメソッドが用意されている、という。直後のthen()にはresolveに渡した結果が使える。

var myPromise = new Promise(function(resolve, reject) {
  resolve('あああああああ〜');
})
.then(result => {
  console.log('result: ',result)  // 'あああああああ〜'
  return '次のメソッドチェーンに渡したい値がここにはいる'
})

console.log(myPromise) // Promise {<resolved>: "ok"}

// myPromiseにさらに then() を足せる!
myPromise.then(passedValue => {
  console.log(passedValue) // '次のメソッドチェーンに渡したい値がここにはいる'
})

then()はずっとつなげることができる。

myPromise.then(passedValue => {
  console.log(passedValue) // '次のメソッドチェーンに渡したい値がここにはいる'
  return ('まだ渡したい値がある')
}).then(passedValue2 => {
  console.log(passedValue2) // まだ渡したい値がある
  return('さすがにこれで終わりにする')
})

ネストがなくてきれいだし、メソッドチェーンで後々呼び出して処理を続けられるのがすごくべんり。

resolveの中で関数を実行する例。

function doSomething(){
    return 1 + 2 + 3
}

var myPromise = new Promise(function(resolve, reject) {
  resolve( doSomething() ); // resolve() の中で関数を実行した!
})
.then(result => {
  console.log('result: ', result) // result:  6
  return result * 100
}).then(result => {
  console.log('result: ', result) // result:  600
  return result
})
myPromise // Promise {<resolved>: 600}

もしresolveを呼ばなかったら(アラームをセットしなかったら)、どうなるのか?

var myPromise = new Promise(function(resolve, reject) {
  return doSomething();
})

myPromise // Promise {<pending>}

myPromiseはずっと実行済みにもエラーにもならない、Pending状態になってしまう。 もちろんそのあとにthen()を続けても実行されない。

グローバル変数に対してのふるまいはこうなる。

var globalNum = 0
var myPromise = new Promise(function(resolve, reject) {
  resolve( globalNum += 1 );
})
.then(result => {
  console.log('1回目: ', globalNum)
  return globalNum += 1
}).then(result => {
  console.log('2回目: ',globalNum)
  return globalNum += 1
}).then(() => {
  console.log('3回目: ', globalNum)
})
console.log('answer: ',globalNum)

実行結果は

answer:  1
1回目:  1
2回目:  2
3回目:  3

となる。

Promiseの中の処理が後続のスクリプトをブロックすることなく、console.log('answer: ',globalNum) が即時実行されている。

Promiseをネストさせる

var smallPromise = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'あああああ');
});

Promise.resolve(1).then(result => {
    return smallPromise // thenブロックの中にPromise
}).then(result => {
    console.log(result) // あああああ
})

当たり前だけど、次のthenはその前のthenがreturn したPromiseがresolveされるのを待機してから実行された。

エラーハンドリング

Promise.resolve(1).then(function(){
    throw new Error('エラーだよ');
}).catch(function(error){
    console.log(error.stack)
})
// Error: エラーだよ
//    at <anonymous>:2:11

Promise.all()

引数に複数のpromiseを配列として受け取り、引数のPromiseすべてがresolveした時点で、自身もresolveする。引数のPromiseたちのうち一つでもrejectされれば、rejectを投げる。 チェーンで続くthen()には、resolveされた値の配列を渡す。

var promise1 = Promise.resolve(3);
var promise2 = Promise.resolve(7);
var promise3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'あああああ');
});

Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values); //  Array [3, 7, "あああああ"]
});
promise3 // Promise {<resolved>: "あああああ"}

async / await

Promiseをもっと簡単に書けるように作られた。

async

関数の前につけるasyncは、その関数がつねにPromiseを返すことを保証する。

var hi = async function f() {
  return 'hi';
}
 // hi()をPromiseとしてthenメソッドをつなぐ
hi().then((result)=>{
    console.log(result) // hi
})

これは、下のコードとおなじになる。

var hi = async function f() {
  return Promise.resolve('hi'); // 明示的にPromiseを渡す
}

hi().then((result)=>{
    console.log(result) // hi
})

もっというと、下のコードとおなじになる。

var hi = new Promise((resolve, reject) => {
    resolve('hi')
})

hi.then(result => { // hi()ではないことに⚠
    console.log(result)
})

await

awaitはasync関数の中で使い、Promiseの結果が返ってくるのを待機することを保証する。つまり、then()を代替して使えるもの。

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000)
  });

  let result = await promise; // ここでPromiseのresolveを待機する

  alert(result); // "done!" // 1秒後にアラートを鳴らす
}

f();

エラーハンドリング

async function f() {

  try {
    let response = await fetch('http://sample-url.com');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

f();