1. ryoyakawai

    No comment

    ryoyakawai
Changes in title
-async/awaitを使ってrequestMIDIAccess()を書いてみる
+requestMIDIAccess()をrequestMIDIAccess()で同期処理的に書いてみる
Changes in body
Source | HTML | Preview
@@ -1 +1,181 @@
-#async/awaitを使ってrequestMIDIAccess()を書いてみる
+この記事は WebAudio/WebMIDI API Advent Calendar 2017 Advent Calendar 2017 の2日目です。
+
+#TL;DR
+Web MIDI APIを使うときに必ず使うAPIが`requestMIDIAccess()`をasync/awaitを使って同期処理的な書き方で書き直して比べてみよう、というのがこのエントリーです。
+
+#PromiseとrequestMIDIAccess()
+「async/awaitの話なのに、なんでPromiseなのか!」といきなり突っ込まれるかもしれませんが、追って説明しますので少々お待ちを。。。😅
+さて、Web MIDI APIを使うときに最初に使うのがこのAPIです。実はこのAPIがWeb APIの中でPromiseが導入された初めてのAPIです(2013年にフラグ付きでShip)。そして現在ではPromiseはgetUserMedia()、Web Bluetooth, Web USBなどのデバイスへのアクセスを必要とするAPIのほとんどで使われるようになっています。
+
+#Promiseは便利なのだけど・・・
+Promiseはとても便利なのですが「直感的な書き方ができないのがな〜・・」と感じたことはありませんか?そして「非同期処理だから仕方ない」と諦めていませんか?
+そこで登場するのがasync/awaitです。async/awaitと使うと非同期処理なのに同期処理のような直感的な書き方をすることが可能です。
+Promiseを使わない場合ですとネストが深くなり、Promiseの場合だと`.then()`がたくさん続くことになります。
+
+#Promiseとasync/awaitの関係
+##Promiseとは
+例えば、引数として数字を渡しその数字が偶数の場合正常終了で「even」と出力、奇数の場合異常終了で「odd」と2秒後に出力するプログラムを考えたとします。
+関数evenodd()が何をしているかを説明すると、
+*「引数を2で割った場合の判定としてゼロならば`resolve()`を実行し、0以外ならば`reject()`を実行」*
+です。
+そして`resolve()`、`reject()`がこの場合は`.then()`で続けて書いた、
+`resolve()` は `() => console.log('even')`
+`reject()` は `() => console.error('odd')`
+に対応しているのです。
+まとめると、Promiseは全体の処理を「成功した場合」と「失敗した場合」の2つに分解して、それぞれの場合、次に何を実行するのかを定義する書き方がPromiseです。それぞれの場合で何を実行するかを`「約束する」`と表現すれば覚えやすいかな、と思います。
+
+````javascript
+evenodd(2).then(() => console.log('even'), () => console.error('odd'));
+function evenodd(num) {
+ return new Promise( (resolve, reject) => {
+ let result = num % 2;
+ setTimeout(() => {
+ if(result == 0) {
+ resolve();
+ } else {
+ reject();
+ }
+ }, 2000);
+ });
+}
+````
+
+そしてPromiseは非同期処理なので、例えevenodd()の一連の処理が終わらなくとも次の処理に進みます。また、続けて処理を`.then()`で続けることも可能です。
+
+##async/awaitとは
+Promiseは非同期処理終了後の処理として`.then()`で定義することが可能ですが、長くなると辛いです。そこが同期的に書けるようにしているのがasync/awaitです。ですが、Promiseが前提として成り立っているので、Promiseの理解なしには、async/awaitも理解仕切れないです。つまりPromiseとasync/awaitは切っても切り離せない関係になっています。
+それでは、上の偶数と奇数の例をasync/awaitに書き換えてみます。`try {...} catch {...}`と書き換えられ、`resolve()`が実行される場合は正常終了、`reject()`が実行される場合は`catch`されるようになりました。Promiseが前提になっていますよね。というか進化系と言ってもいいのかもしれないです。
+
+````javascript
+(async function(){
+ try {
+ await evenodd(2);
+ console.log('even');
+ } catch(error) {
+ console.error('odd');
+ }
+ async function evenodd(num) {
+ return new Promise( (resolve, reject) => {
+ let result = num % 2;
+ setTimeout(() => {
+ if(result == 0) {
+ resolve(true);
+ } else {
+ reject(false);
+ }
+ }, 2000);
+ });
+ }
+}());
+````
+
+というのがPromiseとasync/awaitの関係です。
+同期的な書き方になってとても見やすく感じると思います。いかがでしょう?
+
+#requestMIDIAccessを書き換える
+##Promiseを使って
+馴染みのある書き方ですね。
+
+````html
+<html>
+ <head>
+ </head>
+ <body>
+
+ <div>MIDI Input: <select id="midiinput"></select></div>
+ <div>MIDI Output: <select id="midioutput"></select></div>
+
+ <script>
+ const MIDI = { inputs: [], outputs: [] };
+ startMIDIAccess();
+
+ function startMIDIAccess() {
+ navigator.requestMIDIAccess({sysex:false}).then(successCallback, errorCallback);
+ function successCallback(access) {
+ let iItr = access.inputs.values(), oItr = access.outputs.values();
+ for(let i=iItr.next(); !i.done; i=iItr.next()) MIDI.inputs.push(i.value);
+ for(let o=oItr.next(); !o.done; o=iItr.next()) MIDI.outputs.push(o.value);
+ setDeviceToList('midiinput', MIDI.inputs);
+ setDeviceToList('midioutput', MIDI.outputs);
+
+ //(!!!!....ここに続け処理を書く....!!!!)
+
+ }
+ function errorCallback() {
+ console.error('[ERROR] requestMIDIAccess()', error);
+ }
+ }
+ function setDeviceToList(elemId, devices) {
+ let elem = document.querySelector('#' + elemId)
+ for(let i=0; i<devices.length; i++) {
+ let option = document.createElement('option');
+ option.text = devices[i].name;
+ option.id = devices[i].id;
+ elem.appendChild(option);
+ }
+ }
+ </script>
+ </body>
+</html>
+````
+
+##async/awaitを使って
+上記と同じ内容をasync/awaitで書き換えました。同期的な処理になって分かりやすくなったと思いませんか?
+
+````html
+<html>
+ <head>
+ </head>
+ <body>
+
+ <div>MIDI Input: <select id="midiinput"></select></div>
+ <div>MIDI Output: <select id="midioutput"></select></div>
+
+ <script>
+ 'use strict';
+ (async function(){
+ const MIDI = { inputs: [], outputs: [] };
+ if(await startMIDIAccess() === true) {
+ setDeviceToList('midiinput', MIDI.inputs);
+ setDeviceToList('midioutput', MIDI.outputs);
+
+ //(!!!!....ここに続け処理を書く....!!!!)
+
+ } else {
+ console.error('[ERROR] Not able to get list of MIDI devices.')
+ }
+
+ async function startMIDIAccess() {
+ try {
+ let access = await navigator.requestMIDIAccess({sysex:false});
+ let iItr = access.inputs.values(), oItr = access.outputs.values();
+ for(let i=iItr.next(); !i.done; i=iItr.next()) MIDI.inputs.push(i.value);
+ for(let o=oItr.next(); !o.done; o=iItr.next()) MIDI.outputs.push(o.value);
+ return true;
+ } catch(error) {
+ console.error('[ERROR] requestMIDIAccess()', error);
+ return false;
+ }
+ }
+ function setDeviceToList(elemId, devices) {
+ let elem = document.querySelector('#' + elemId)
+ for(let i=0; i<devices.length; i++) {
+ let option = document.createElement('option');
+ option.text = devices[i].name;
+ option.id = devices[i].id;
+ elem.appendChild(option);
+ }
+ }
+ }());
+ </script>
+</html>
+````
+
+## 結局、何がいいの?
+まずは見やすくなったと思います。
+更に上記のMIDIデバイスをリストしてさらにその先の処理を続けて書く場合を考えてみましょう。
+Promise版、async/await版のどちらにも``(!!!!....ここに続け処理を書く....!!!!)``を挟んでいますが、ここに続けて処理を追加して書くことになるのです。Promise版、async/await版のどちらがよさそうですか?
+個人的にはasync/await版のが見やすくなったと感じますし、メンテナンスをすることを考えると直感的で分かりやすい書き方になっていてうれしいです。
+
+#まとめ
+async/await推し推しで書いてしまいましたが、実際に使ってみると「Promiseには戻れない・・・」と私は感じています。ほんとに見通しもよく、キレイでそして便利なんですよね。ということで、気になる方は是非試してみてくださいっ🎄