Help us understand the problem. What is going on with this article?

JavaScriptと非同期処理の話

More than 1 year has passed since last update.

JavaScriptと非同期処理の話

by KuwaK
1 / 40

※身内での勉強会の資料です

使ったソースは全部GitHubに上げてあります

環境

$ node -v
v8.9.3

JavaScriptの歴史、それは非同期処理との戦いの歴史


プログラムの常識

コードは上から順に実行される


sync.js
const fs = require('fs');

var hoge = fs.readFile("hoge.txt")
var fuga = fs.readFile("fuga.txt") 
var foo = fs.readFile("foo.txt") 
var bar = fs.readFile("bar.txt")
console.log(hoge + fuga + foo + bar);

動かない


なんで?

  • 非同期処理だから

  • ファイルの読み込みが終わる前に console.log() が走っちゃう


なんで非同期なの?

  • 同期的な処理だと、IOアクセスの待ち時間中に何もできなくなる
  • DOMを操作するときはなにかと非同期で動かしたいことが多い(Ajaxのレスポンスを待っている間にプログレスバーを表示させるとか)

ES6以前の非同期処理


コールバック地獄

async.js
const fs = require('fs');

fs.readFile("hoge.txt", function (err, hoge) {
    fs.readFile("fuga.txt", function (err, fuga) {
        fs.readFile("foo.txt", function (err, foo) {
            fs.readFile("bar.txt", function (err, bar) {
                console.log(hoge + fuga + foo + bar);
            });
        });
    });
});

  • 深いネスト

  • エラーをまとめて受け取れない

  • function ,function , function ...


ES6 (ECMAScript 2015)以後の世界


Promise


Promiseは非同期処理を抽象化したオブジェクトとそれを操作する仕組みのことをいいます。
-- Promiseの本より


書き方

promise.js
var promise = (str) => {
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, 100, str);
    })
};

promise("foo").then(
    (val) => {
        console.log(val)
        return promise("bar")
    }
).then(
    (val) => { console.log(val) }
).catch(
    (err) => { console.log(err) }
)


解説

Promiseオブジェクト

  • Promiseオブジェクトを返す関数を書く
  • Promiseオブジェクトの中に非同期に行いたい処理を書く
  • Promise関数内の処理が終わったらresolve()を呼び出す
  • Promise関数内の処理が失敗したらreject()を呼び出す

解説

Promiseオブジェクトを使う

  • promiseを実行したあとに実行したい処理をthen節の中に書く
  • 実行結果はvalで受け取る
  • promiseの処理が失敗したらcatch節で受け取れる

いいところ

  • ネストが深くならない
  • 各処理で起こったエラー(例外)をcatch節でまとめて補足できる

これでコールバックともおさらばだぜ!!


promise.js
const fs = require('fs')
const util = require('util')

const readFileAsync = util.promisify(fs.readFile)

var hoge, fuga, foo, bar

readFileAsync("hoge.txt")
    .then(
        (result) => { 
            hoge = result
            return readFileAsync("fuga.txt") }
    ).then(
        (result) => {
            fuga = result 
            return readFileAsync("foo.txt") }
    ).then(
        (result) => {
            foo = result
            return readFileAsync("bar.txt") }
    )
    .then(
        (result) => { 
            bar = result
            console.log(hoge + fuga + foo + bar) }
    )

補足

  • コールバック関数をPromiseに変換できるpromisifyってライブラリがある

  • fs.readFile()はコールバック関数なので今回はこれを使って readFileAsync って関数を作ってます


うーん、微妙・・・


  • 確かにネストは浅くなった
  • でも長い
  • 外の変数に退避しないと一個前の結果を参照できない
  • then,then,then...

もっと同期的に書きたい

理想のコード.js
const fs = require('fs');

var hoge = fs.readFile("hoge.txt")
var fuga = fs.readFile("fuga.txt") 
var foo = fs.readFile("foo.txt") 
var bar = fs.readFile("bar.txt")
console.log(hoge + fuga + foo + bar);

そして ES8(ES2017)へ・・・


async/awaitの登場


await.js
const fs = require('fs');
const util = require('util');

const readFileAsync = util.promisify(fs.readFile);

async function readFiles(){
    var hoge = await readFileAsync("hoge.txt")
    var fuga = await readFileAsync("fuga.txt") 
    var foo = await readFileAsync("foo.txt") 
    var bar = await readFileAsync("bar.txt")
    console.log(hoge + fuga + foo + foo);
}

readFiles()

よい


解説

  • 同期的に処理したい部分を async function で囲む
  • 同期的に処理したいPromise関数の手前に await をつける
  • Promiseの処理が終わるまで待ってくれるようになる

ちなみに・・・

  • async functionもPromiseを返すので、以下のようなコードは非同期に実行されます
await1.js
const fs = require('fs');
const util = require('util');

const readFileAsync = util.promisify(fs.readFile);

async function readFiles(){
    var hoge = await readFileAsync("hoge.txt")
    var fuga = await readFileAsync("fuga.txt") 
    var foo = await readFileAsync("foo.txt") 
    var bar = await readFileAsync("bar.txt")
    console.log(hoge + fuga + foo + bar);
}

readFiles()

console.log("readFiles()はまだ終わらない・・・")
  • ちゃんとやるなら以下のような感じ
await2.js
const fs = require('fs');
const util = require('util');

const readFileAsync = util.promisify(fs.readFile);

async function readFiles() {
    var hoge = await readFileAsync("hoge.txt")
    var fuga = await readFileAsync("fuga.txt")
    var foo = await readFileAsync("foo.txt")
    var bar = await readFileAsync("bar.txt")
    console.log(hoge + fuga + foo + bar);
}

readFiles().then(
    () => { console.log("readFiles()は終わった!!") }
)

めでたしめでたし


ちょっとまって


そもそも各ファイルを同期的に読み込む必要ないよね?


なんで非同期なの?

  • 同期的な処理だと、IOアクセスの待ち時間中に何もできなくなる

⇛ 非同期で実装されているにはそれなりの理由がある


Promise.all()


Promise.all()

  • 複数の非同期処理をまとめて実行したあとにthen節を呼び出せる

promise_all.js
const fs = require('fs');
const util = require('util');

const readFileAsync = util.promisify(fs.readFile);

var hoge = readFileAsync("hoge.txt")
var fuga = readFileAsync("fuga.txt")
var foo = readFileAsync("foo.txt")
var bar = readFileAsync("bar.txt")

Promise.all([hoge, fuga, foo, bar])
    .then(
        (values) => { console.log(values[0] + values[1] + values[2] + values[3]) }
    )


awaitとの合わせ技もできるよ!!

promise_all1.js
const fs = require('fs');
const util = require('util');

const readFileAsync = util.promisify(fs.readFile);

async function readFiles() {
    var hoge = readFileAsync("hoge.txt")
    var fuga = readFileAsync("fuga.txt")
    var foo = readFileAsync("foo.txt")
    var bar = readFileAsync("bar.txt")

    var values = await Promise.all([hoge, fuga, foo, bar])
    console.log(values[0] + values[1] + values[2] + values[3])
}

readFiles()

まとめ

  • async/awaitは神
  • Promise.allとかも使って、非同期にすべきところは非同期にしよう
  • ちなみに新しい規格なのでIE非対応(えっ・・・)。使いたいときはトランスコンパイルしましょう

おしまい


KuwaK
TypeScriptが好きです
fujitsu
富士通グループのソフトウェア技術者有志により運営しているコミュニティです。
http://www.fujitsu.com/jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away