フラミナル

考え方や調べたことを書き殴ります。IT技術系記事多め

JavaScriptにおけるコールバック関数とPromiseについて

JavaScript Logo

JavaScriptのコールバック関数やPromise周りの勉強をする際にわかりやすかったサイトと備忘をまとめます。

コールバック関数について

おすすめ→JavaScriptの「コールバック関数」とは一体なんなのか

以下は記事を読みながら実際にかいてみたコードです。

特に驚きだったのは変数に関数を代入できるvar sum = add)ところですね、これは他の言語ではまずみないです。

function add(a,b) {
  return a + b;
}

// 変数に既存の関数を代入する
var sum = add;

// 変数自体に関数を指定する(無名関数)
var sumA = function(a, b) { return a + b };

// 関数の中から関数を呼び出す
var sumB = function(a, b) { return add(a, b) };

// 変数自体に関数を指定する(アロー関数)
var sumC = (a, b) => { return a + b };

// 関数の中から関数を呼び出す
var sumD = (a, b) => { return add(a, b) };


console.log(sum(1, 2))
console.log(sumA(1, 2))
console.log(sumB(1, 2))
console.log(sumC(1, 2))
console.log(sumD(1, 2))

結果はすべて3と出力される

次に試したところも驚きで、関数に対して関数を渡せるのもこれまでのプログラミングで培ったものと直感に反していました。var sumE = function a(func) { return func(1, 2); }

// -----------------------------

// 通常の関数+引数に関数を受け取る
// 関数を受け取る関数を「高階関数」という
var sumE = function a(func) { return func(1, 2); }

// アロー関数+引数に関数を受け取る
// 関数を受け取る関数を「高階関数」という
var sumF = (func) => { return func(1, 2); }

// add関数をsumE/sumFに渡して計算してもらう
console.log(sumE(add))
console.log(sumF(add))

// -----------------------------

結果はすべて3と出力される

// コールバック関数とは「高階関数に渡すための関数」
// console.log(sumE(add)) における add のこと


setTimeout(function() { console.log('Hello!');}, 2000);

2000ミリ秒後にHello!と表示される

Promiseについて

Promiseの前にコールバック地獄について知る

コールバック関数を多用するとどんどんネストが深くなることで可読性が落ちたり、バグの温床になる可能性があります。

test1(function(data1) {
  test2(function(data2) {
    test3(function(data3) {
      // 何かの処理
    }
  }
}

これをコールバック地獄と呼びます。

Promiseはこれを解消するための方法です。

参考:Promiseとasync/awaitでJavaScriptの非同期処理をシンプルに記述する

Promiseの使い方

promise = new Promise(function (resolve, reject) {
  console.log(123)
  resolve();
});

promise.then(
  console.log(456)
);

このコードを実行すると以下のように表示されます。

123
456

さて、何が起きているのかを順を追ってみていきましょう。

まずはここですね。

promise = new Promise(function (resolve, reject) {
  console.log(123)
  resolve();
});

ここではPromise型の変数promiseを定義しています。(名前はなんでも良いです)

promiseの第1引数にはコールバック関数が指定されておりconsole.log(123)resolve()というものがありますね。console.logは画面に表示するだけのものなのでresolve()について考えてみましょう。

resolve()は「解決する」という意味を持ちますが、ここでのresolve()の役割は変数promiseで規定されていた処理が終わったので次の処理に進んでいいよというものです。

resolve()が呼び出されると次はthen句に移動します。

この処理ではthenの中にある処理を実行しているだけですね。

promise.then(
  console.log(456)
);

よって以下のように表示されるわけです。

123
456

Promiseをうまく使うとこのようにスッキリ書くことができます。

function printAsync(text, delay) {
    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(text);
            resolve();
        }, delay)
    });
    
    return p;
}

printAsync('hello', 500)
    .then(() => printAsync('world', 500))
    .then(() => printAsync('lorem', 500))
    .then(() => printAsync('ipsum', 500));

Promiseとasync/awaitでJavaScriptの非同期処理をシンプルに記述するよりコードを引用

ちなみに、then以降に変数を渡す場合は以下のようにすることでresolve()経由で変数を渡すことができます。

promise = new Promise(function (resolve, reject) {
  console.log(123)
  abc = 456
  resolve(abc);
});

promise.then(function(val){
  console.log(val);
});

Promiseでもネストは起きる

しかし共通化できない処理の場合はPromise自体がネストしてしまいます。

new Promise((resolve) => {
  resolve(3);
}).then((v1) => { // v1 は 3
  new Promise((resolve) => {
    resolve(v1 * 3);
  }).then((v2) => { // v2 は 9
    new Promise((resolve) => {
      resolve(v2 * 4);
    }).then((v3) => { // v3 は 36
      console.log(v3); // 36 が出力される
    });
  });
});

これはこまりましたね。

Promiseにはthen関数に渡す関数内でreturnを使うことで以下のようにできます。

new Promise((resolve) => {
  resolve(3);
}).then((v1) => { // v1 は 3
  return new Promise((resolve) => {
    resolve(v1 * 3);
  });
}).then((v2) => { // v2 は 9
  return new Promise((resolve) => {
    resolve(v2 * 4);
  });
}).then((v3) => { // v3 は 36
  console.log(v3); // 36 が出力される
});