Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 1 year has passed since last update.

jsで直列実行 p.s. 非同期処理とは

Last updated at Posted at 2022-02-28

非同期処理ってなに?なんで必要?

難しい単語ですよね。よくわかる解説をどうぞ

ふつうのコード.js
// 1. 重い処理(3秒かかっちゃう)
console.log('重い処理');

// 2. かるい処理(すぐおわる)
console.log('かるい処理');

こういう並びでコードを実行したかったり

ふつうのコード.js
// 1. 重い処理
console.log('重いよぅ');

// 2. その次じゃないとやっちゃいけない処理
console.log('重い処理おわらないとやっちゃだめー!');

コードの並び順通りに実行しないとダメ!って場合、ありません?
例えば

ふつうのコード.js
// 1. サーバとかDBから値をもらう
// 2. もらった値をコネコネする

みたいな処理をしたい場合、サーバから値もらってないのにコネコネできないよね。
ほかにも

オンライン対戦.js
// Aさんが操作する
// Aさんのターンが終わってからBさんのばん。Aさん終わるまでBさん触っちゃダメ。

ってとき、行の順番通りにコードを動かしたいし、上の行が終わるの待ちたいと。
え、そもそも待たないんだ!?って人もいるかもですね。その通り、待てば問題なし。

Ajaxや非同期処理を使いたい

待てばいいんだけどさ、待ったら以降すべての処理がとまっちゃいますよ、と。
とても遅いコードになってしまうし、重い画面になる。
なので、サーバ接続したりする処理だけ別枠にしたい。そうです。待たないんです。
画面を描画するサーバから値をもらう。「同時に」やってしまうっていうのが、「非同期処理」の便利なところだな。

こういうこと
image.png

つまり、「サーバから値もらう」というのと、「値が帰ってきたら = 処理が終わったら 画面に表示する」を
順番通りに、直列にやりたいよね。

そう。
非同期処理が世の中で流行ってるから、非同期処理の中で、直列に処理したい ということなのだ。

「重い処理」を実際に試してみましょう。
chromeでF12押して、devツールのコンソールのとこでjsが簡単に実行できますよね。試してみてね。

試してみよう.js
// 1. 重い処理(3秒後にconsole.logが実行されるよ)
setTimeout(() => console.log('重い処理おわる'), 3000);

// 2. その次じゃないとやっちゃいけない処理
console.log('重い処理おわらないとやっちゃだめー!');

結果は

重い処理おわらないとやっちゃだめー!
重い処理おわる

となっちゃう。試した?ほんと?
試してないじゃん。しょうがないなぁ。ほれこれ見て。

See the Pen Untitled by serna37 (@serna37) on CodePen.

重い処理, その後の処理となってほしいのに、「, その後の処理」となったあとで「重い処理」となってしまっているね。

実はこれは、setTimeouが非同期処理なんですね。
3秒まつ、という処理が
「上の行から順番に実行し始めるけど、その行が終わってなくてもドンドン次にいってしまう」のです。

ここでいえば

1. 「3秒待ってからconsole.log(重い処理)」を実行開始
2. console.log(その後の処理)が実行開始
3. console.log(その後の処理)がクッソはやく処理完了する
4. ようやく3秒たって、console.log(重い処理)がやっと処理完了する

といった流れ。
わざわざsetTimeoutで遅れさせているけれど、サーバから値をとる、などの重い処理の代わりになっているはず。

前置きが長くなりましたが、これを解決するために「非同期処理」というものがあります。
では非同期処理ってなに?

やりたいことはこう

これがやりたい.js
// 1-a. 重い処理をやる
// 1-b. 重い処理が終わるまで待つ

// 2. 次の処理にいく

ここで登場するのがPromiseです。
jsにはPromiseってものがあるのです。偉い人が作ってくれました。ありがとう偉い人。
Promiseの正確な説明はMDNのサイトを見てね。

これで解決だ!.js
new Promise(resolve => {
  // 重い処理
  setTimeout(() => {
    console.log('重い処理おわる')
    // resolveを実行したら次にすすむよ
    resolve();    
  }, 3000);
})
.then(() => console.log('その後の処理'));

See the Pen Untitled by serna37 (@serna37) on CodePen.

解決しましたね。
無事、重い処理が終わるまで待ってから、次の処理を開始できました。

Promiseの使い方

公式がすべてですが一応解説。
覚えておくことは2つで、
new Promise()は引数に関数をとる
resolve()を実行すると、そこで終わる。
くらいですね。(エラー時の挙動とかは今回は省きます。公式見てね)

Promiseオブジェクトは、中で「状態」を持っています。new Promiseしたオブジェクト自体をconsole.logしてみるとよくわかる。
image.png
resolveしないままだと、PromiseStateとやらが"pending"になっている。保留してくれているのだ。
じゃあresolve()すると

image.png

PromiseStateが"fulfilled"になっているね。完了したで!って意味。次の処理行っていいよ!ってことだね。

だから、つなぎまくってこんなこともできる

いっぱいやる.js
new Promise(resolve => {
  setTimeout(() => {
    console.log('1番目');
    resolve();    
  }, 3000);
})
.then(() => new Promise(resolve => {
  setTimeout(() => {
    console.log('2番目');
    resolve();    
  }, 3000);
}))
.then(() => new Promise(resolve => {
  setTimeout(() => {
    console.log('3番目');
    resolve();    
  }, 3000);
}))
.then(() => new Promise(resolve => {
  setTimeout(() => {
    console.log('4番目');
    resolve();    
  }, 3000);
}))
;

なげぇ...
もっときれいに書きたいね。

直列実行

ようやくタイトル回収。きれいに書きましょう。

SeriesExe.js
    /**
     * 関数を直列に同期実行. 以下の形式の関数の配列を引数とする.
     * (v, r) => {任意の実装, r(v)を実行したタイミングで完了}
     * 例)
     * const sampleFunction = (v, r) => {
     *   // 任意の実装
     *   setTimeout(() => {
     *     // 遅延して完了し, 次の関数へ
           r(v);
     *   }, 1000);
     * }
     * [sampleFunction, sampleFunction, ...]が引数となる.
     *
     * @param arr 直列実行する関数の配列 (index順)
     * @return arrを全て実行したあとのPromiseを返却
     */
    const seriesExe = arr => {
      return arr
        .reduce((x, y) => x.then(v => new Promise(r => y(v, r))),
        Promise.resolve());
    };

こいつらを直列に実行してみよう

test.js
let test1 = (v, r) => {
  setTimeout(() => {
    console.log('test1');
    r('test2');
  }, 3000);
};

let test2 = (v, r) => {
  setTimeout(() => {
    console.log(v);
    r('test3');
  }, 3000);
};

let test3 = (v, r) => {
  setTimeout(() => {
    console.log(v);
    r('end');
  }, 3000);
};

let ending = (v, r) => {
  setTimeout(() => {
    console.log(v);
    r();
  }, 3000);
};
つかう.js
seriesExe([test1, test2, test3, ending]);

// このあとにさらに続けることもできる PromiseAllとかしてもいいね
seriesExe([test1, test2, test3, ending]).then(() => ..().);

サンプルデモ

See the Pen Untitled by serna37 (@serna37) on CodePen.

ということでした。ちょいちょい大事なとこ飛ばしてるけど、詳しいところは公式読みつつ手を動かしつつ理解しましょう!
いじょうです

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?