Open4

『Promiseの本』を読んでおさらい

shimotsushimotsu

1章

いちばん基本の形

function fetchURL(URL) {
  return new Promise((resolve, reject) => {
    const req = new XMLHttpRequest();
    req.open("GET", URL, true);
    req.onload = () => {
      if (200 <= req.status && req.status < 300) {
        // Promise オブジェクトが解決 → fulfilled 状態になる
        resolve(req.responseText);
      } else {
        reject(new Error(req.statusText));
      }
    };
    req.onerror = () => {
      reject(new Error(req.statusText));
    };
    req.send();
  });
}

const URL = "https://httpbin.org/status/500";
fetchURL(URL)
  .then(function onFulfilled(value) {
    console.log(value);
  })
  .catch(function onRejected(error) {
    console.error(error);
  });

状態について

Promise は、以下の3つの状態しかない。

  • Fulfilled:resolve(成功) したとき
  • Rejected:reject(失敗)したとき
  • Pending:resolveでもrejectでもないとき。初期状態など。
shimotsushimotsu

2章

以下の2つは同義。上の書き方は、下の書き方のシンタックスシュガー。

Promise.resolve(42);
new Promise((resolve) => {
    resolve(42);
});

Promise chain

Promise は .then().catch() をメソッドチェーンで書ける。

function taskA() {
    console.log("Task A");
}
function taskB() {
    console.log("Task B");
}
function onRejected(error) {
    console.log("Catch Error: A or B", error);
}
function finalTask() {
    console.log("Final Task");
}

const promise = Promise.resolve();
promise
    .then(taskA)
    .then(taskB)
    .catch(onRejected)
    .then(finalTask);

then の使い方

NG

function badAsyncCall() {
    const promise = Promise.resolve();
    promise.then(() => {
        // 何かの処理
        return newVar;
    });
    return promise;
}

OK → Promise.then によって新たに作られた Promise オブジェクトが返される

function anAsyncCall() {
    const promise = Promise.resolve();
    return promise.then(() => {
        // 何かの処理
        return newVar;
    });
}

Promise.all()

複数の Promise オブジェクトの結果をまとめて取得 & 処理する。

function fetchURL(URL) {
    return new Promise((resolve, reject) => {
        const req = new XMLHttpRequest();
        req.open("GET", URL, true);
        req.onload = () => {
            if (200 <= req.status && req.status < 300) {
                resolve(req.responseText);
            } else {
                reject(new Error(req.statusText));
            }
        };
        req.onerror = () => {
            reject(new Error(req.statusText));
        };
        req.send();
    });
}

// comment, people それぞれの Promise オブジェクトを返す
const request = {
    comment() {
        return fetchURL("https://azu.github.io/promises-book/json/comment.json").then(JSON.parse);
    },
    people() {
        return fetchURL("https://azu.github.io/promises-book/json/people.json").then(JSON.parse);
    }
};
function main() {
    return Promise.all([request.comment(), request.people()]);
}



// 実行例
main().then((value) => {
    // [comment, people]の順番で出力される
    //  Promise.all([request.comment(), request.people()]) の実行時は同時だが、
    // Promise の結果は、配列に格納された順となる
    console.log(value);
}).catch((error) => {
    console.error(error);
});
shimotsushimotsu

5章

async / await の基本形

ポイントは、await式の右辺にあるPromiseのresolveされた値が左辺の変数へと代入されるので、非同期処理を同期処理のように記述できる点。

// `async`をつけて`fetchBookTitle`関数をAsync Functionとして定義
async function fetchBookTitle() {
    // リクエストしてリソースを取得する
    const res = await fetch("https://azu.github.io/promises-book/json/book.json");
    // レスポンスをJSON形式としてパースする
    const json = await res.json();
    // JSONからtitleプロパティを取り出す
    return json.title;
}

// `async`をつけて`main`関数をAsync Functionとして定義
async function main() {
    // `await`式で`fetchBookTitle`の非同期処理が完了するまで待つ
    // `fetchBookTitle`がresolveした値が返り値になる
    const title = await fetchBookTitle();
    console.log(title); // => "JavaScript Promiseの本"
}

main();
  • Async Function は必ず Promise を返す
  • Async Function内ではawait式が利用できる

await

await 式では非同期処理を実行し完了するまで、次の行(次の文)を実行しない。

// async functionは必ずPromiseを返す
async function doAsync() {
    // 非同期処理
}
async function asyncMain() {
    // doAsyncの非同期処理が完了するまでまつ
    await doAsync();
    // 次の行はdoAsyncの非同期処理が完了されるまで実行されない
    console.log("この行は非同期処理が完了後に実行される");
}

await 式では Fulfilled となった場合、 resolve された値が返り値となる。

async function asyncMain() {
    const value = await Promise.resolve(42); // この右辺の返り値は 42 となる
    console.log(value); // => 42
}
asyncMain(); // Promiseインスタンスを返す

async / await の try...catch 構文

await 式でのエラーは try…​catch 構文でキャッチできる。

async function asyncMain() {
    // await式のエラーはtry...catchできる
    try {
        // `await`式で評価した右辺のPromiseがRejectedとなったため、例外がthrowされる
        const value = await Promise.reject(new Error("エラーメッセージ"));
        // await式で例外が発生したため、この行は実行されません
    } catch (error) {
        console.log(error.message); // => "エラーメッセージ"
    }
}
asyncMain().then(() => {
    console.log("この行は実行されます");
}, (error) => {
    // すでにtry...catchされているため、この行は実行されません
});

複数の非同期処理の例

for ループのなかで await 式を用い、結果を配列に push している。

function dummyFetch(path) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (path.startsWith("/resource")) {
                resolve({ body: `Response body of ${path}` });
            } else {
                reject(new Error("NOT FOUND"));
            }
        }, 1000 * Math.random());
    });
}
// 複数のリソースを取得し、レスポンスボディの配列を返す
async function fetchResources(resources) {
    const results = [];
    for (let i = 0; i < resources.length; i++) {
        const resource = resources[i];
        const response = await dummyFetch(resource);
        results.push(response.body);
    }
    return results;
}
const resources = ["/resource/A", "/resource/B"];
// リソースを取得して出力する
fetchResources(resources).then((results) => {
    console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"]
});

async 関数の外側ではメインスレッドの処理は進んでいく

Array.forEach() で async 関数をコールバック関数として実行しているが、その外側では fetchResources 関数は処理進めてしまう。

function dummyFetch(path) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (path.startsWith("/resource")) {
                resolve({ body: `Response body of ${path}` });
            } else {
                reject(new Error("NOT FOUND"));
            }
        }, 1000 * Math.random());
    });
}
// リソースを順番に取得する
async function fetchResources(resources) {
    const results = [];
    resources.forEach(async(resource) => {
        const response = await dummyFetch(resource);
        results.push(response.body);
    });
    return results;
}
const resources = ["/resource/A", "/resource/B"];
// リソースを取得して出力する
fetchResources(resources).then((results) => {
    // resultsは空になってしまう
    console.log(results); // => []
});

解決策として、 Promise.all() を活用する手段がある。

function dummyFetch(path) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (path.startsWith("/resource")) {
                resolve({ body: `Response body of ${path}` });
            } else {
                reject(new Error("NOT FOUND"));
            }
        }, 1000 * Math.random());
    });
}

// 複数のリソースを取得しレスポンスボディの配列を返す
async function fetchResources(resources) {
    // リソースをまとめて取得する
    const promises = resources.map((resource) => {
        return dummyFetch(resource);
    });
    // すべてのリソースが取得できるまで待つ
    // Promise.allは [ResponseA, ResponseB] のように結果が配列となる
    const responses = await Promise.all(promises);
    // 取得した結果からレスポンスのボディだけを取り出す
    return responses.map((response) => {
        return response.body;
    });
}
const resources = ["/resource/A", "/resource/B"];
// リソースを取得して出力する
fetchResources(resources).then((results) => {
    console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"]
});