※スマホ対応はしてません。

テンプレート記法で簡単文字列組み立て。(現代的JavaScriptおれおれアドベントカレンダー2017 – 12日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 12日目

概要

back-tick (back-quote) ` … ` で括ると「テンプレートリテラル」となり、変数を埋め込んだりできます。

const user = { name: 'Alice', birthYear: 2000 };
const message = `${user.name} is ${new Date().getFullYear() - user.birthYear}-year-old.`;

他に改行したり、独自のテンプレート関数を適用させることも。

テンプレートリテラル

今まで動的な情報を使って文字列を組み立てる場合、 + で接続してあれこれしてきました。

console.log('timestamp: ' + Date.now() + ' at some place.');

テンプレートリテラルの場合、 ${ … } という書き方を使って文字列中に埋め込むことで、より読みやすく記述することができます。

console.log(`timestamp: ${Date.now()} at some place.`);

うは、Syntax highlightingが間に合ってないすね。

式

${ … } の中身には式を書くことができます。

console.log(`result: ${1 + 2}`);

+ 連結時と同様、 .toString() が呼び出され文字列になります。

式は書けるけど文は書けません。 for とかは駄目。

改行

テンプレートリテラル中では改行もそのまま使えます。

console.log(`Hello
             Beautiful
             World!!`);
Hello
             Beautiful
             World!!

インデントはアレなんだけど。

今まで通り \n での改行もできます。

生文字列

back tickの前に「タグ (tag) 」を置くことで、特別なテンプレート化を行うことができます。

String.raw() というのが用意されていて、これを使うと文字列を「そのまま」の状態で扱えます。

console.log("Ginpei \"Sushi-guy\" Takanashi");  // Ginpei "Sushi-guy" Takanashi
console.log(`Ginpei \"Sushi-guy\" Takanashi`);  // Ginpei "Sushi-guy" Takanashi
console.log(String.raw`Ginpei \"Sushi-guy\" Takanashi`);  // Ginpei \"Sushi-guy\" Takanashi

back slash \ でエスケープできるはずが、されなくなります。もちろん今まで通りの \n でも改行できなくなってしまいました。

改行や ${ … } による処理埋め込みは可能です。そして、当たり前だけど、そっちで普通の文字列にしたらエスケープされます。

console.log(String.raw`\'
${'\''}`);
\'
'

無駄にややこしくしちゃったかも。ごめんね。

普通の関数として使ってはいけない

これは駄目。

console.log(String.raw(`\n`));  // Exception: TypeError: can't convert undefined to object

この↑場合だと、先にテンプレートリテラルを普通に解釈して、その結果の文字列を与えることになります。

括弧なしで後ろに付けてください。(空白はあってもなくてもよろしい。)

console.log(String.raw `\n`);  // \n

自分でテンプレート関数を作るようになったらよくわかるかも。

タグ関数

String.raw() みたいなものをタグ関数 (tag function) 、タグ関数を伴うテンプレートリテラルの仕組みをタグ付きテンプレート (tagged template) と呼ぶようです。

加熱調理済み紐

ちなみにエスケープしない生の文字列のことを “raw strings” 、そうでないエスケープされるものを “cooked strings” と呼ぶみたいです。

独自テンプレート

前項 String.raw() は、使い方は特殊だけど結局は関数です。同じような関数を作成することで、独自のテンプレート処理を行うことができます。

別稿参照。

その他

この記号 ` の名前

MDN (en)だと “back-tick” と呼んでいる。

Wikipediaには “grave accent” の名前で項目があり、プログラミング界隈での別名として “backquote” 、 “backtick” が紹介されている。

Programmers use the grave accent symbol as a separate character (i.e., not combined with any letter) for a number of tasks. In this role, it is known as a backquote or backtick.

テンプレートリテラル vs テンプレート文字列

仕様的には “template literals” 。

MDNによると過去に「テンプレート文字列」と呼ばれていたみたいです。

ES2015 / ES6 仕様の以前のエディションでは、”template strings” と呼ばれていました。

参考

非同期やるならasync/awaitでもっとらくらく。(現代的JavaScriptおれおれアドベントカレンダー2017 – 11日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 11日目

概要

Promise を使った非同期の処理をさくさく書けるやつです。

簡単な例としては、まず Promise オブジェクトを返す関数↓があったとして、

// 指定ms待つ
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

このコード↓を

function sayAfterSleeping(message) {
    console.log('ぐー……');
    sleep(1000)
        .then(() => {
            console.log('むにゃ……');
            return sleep(1000);
        })
        .then(() => {
            console.log('あ、ごめん寝てた。えっと……');
            return sleep(1000);
        })
        .then(() => {
            console.log(message);
        })
}

sayAfterSleeping('Hello Async World!');

こう↓書けます。

async function sayAfterSleeping(message) {
    console.log('ぐー……');
    await sleep(1000);

    console.log('むにゃ……');
    await sleep(1000);

    console.log('あ、ごめん寝てた。えっと……');
    await sleep(1000);

    console.log(message);
}

sayAfterSleeping('Hello Async World!');

基本的な使い方

Promise を使ったコードを、前述のようにそれなりに簡単に置き換えることができます。

  1. Promise を返す関数の呼び出しに await を付ける
  2. await を使う関数に async を付ける

これだけです。

忘れないでね

async を付けるのは Promise オブジェクトを返す関数ではなく、 await を使う方の関数という点にご注意ください。逆に Promise オブジェクトを返すやつに async を付ける必要は(その中で await を使っていないなら)ないです。

あ、あと await を書けるのは async を付けた関数の中だけです。

ここら辺最初のころは忘れたり混同したりしがちでは。(個人の感想です。)

結果を受け取る

then() で登録するコールバック関数実行時に結果を受け取ることができましたが、 await の場合は単純な戻り値のような形で扱えます。

// fetch()とformat()はダミーです

function fetchDetail(id) {
    return fetch(`/api/foo/${id}`)  // Promiseを返す
        .then((res) => {
            return format(res);  // 結果を整形
        });
}

async function showDetail(id) {
    const detail = await fetchDetail(id);  // 待ってから結果を受け取る
    console.log(detail.name, detail.type);
}

showDetail(123);

Fetch APIの話じゃないです。

for 文

実践編。

間を置きながら n 回数えて終了する countdown() という処理を考えます。

真ん中が本体です。

// 指定ms待つ
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function countdown(n) {
    let p = Promise.resolve();
    for (let i = 0; i < n; i++) {
        p = p.then(() => {
            console.log(`${n - i}...`);
            return sleep(1000);
        });
    }
    return p;
}

countdown(3)
    .then(() => {
        console.log('done!')
    })

なんかややこしいすね。

単純な for 文だと駄目です。だって Promise 完了まで待たないとすぐ終わっちゃうから。なので、 Promise オブジェクトを変数 p で覚えておいて、さらにそこから then() の戻り値で上書きしながら連結します。(ちなみに let を var にするとだめです。)

これ↑を、 await を使うことで、こう↓めっちゃ簡単に書けるようになります。

// 指定ms待つ
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function countdown(n) {
  for (let i = 0; i < n; i++) {
    console.log(n - i, '...');
    await sleep(1000);
  }
}

countdown(3)
  .then(() => {
    console.log('done!')
  })

ほら、さっき駄目だった「ただの for 文」で済んでしまいました。読みやすーい。

宣言、式、メソッド

各種取り揃えております。

async function foo() {}

const bar = async function() {};

const obj = {
  async hoge() {},
};

class Klass {
  async fuga() {}
}

例外

Promise オブジェクトの catch() は、普通の try-catch で実現できます。

async function() {
  try {
    const result = await doAsync();
  }
  catch (err) {
    console.error(err);
  }
}

いろいろな await

途中に await

わりとどこにでも await を突っ込むことができるようです。

function returnLater(result) {
  return new Promise(resolve => setTimeout(() => resolve(result), 1000));
}

async function af () {
  console.log('...');
  console.log(await returnLater(1) + await returnLater(2));  // 3
}

af();

ただ await 出現の時点で待機を開始するので、複数書いても並列に実行はしません。上記の例では1000 ms待つのを二回呼んでるので、結果が出てくるまで2000 msかかります。

await new Promise()

というのもできます。要は右辺が Promise オブジェクトなら良いわけで、関数実行は結果としてそれを満たすだけです。

async function foo() {
  await new Promise(resolve => setTimeout(resolve, 1000));
  console.log('おまたせ!');
}

await 123

というのもできます。右辺が Promise オブジェクトだったら待つんだけど、そうでなければそのまま流します。

await await await p

というようにたくさん並べることもできます。

できるけどしないね。(関数呼び出しの中身を追っていくと結果的にこうなる場面はいくらでもあるだろうけど。)

ES2017

ES2015 (ES6)じゃなくてES2017で追加されました。

ChromeとFirefoxではもう使えます。それ以外で使うならバベってください。

その他

async 後に改行は駄目

だめです。

async
function foo() {}
// Exception: ReferenceError: async is not defined

まあこんなのしないよね。

Function オブジェクトではない

async 付きで宣言したものは、コンストラクタが Function ではなく AsyncFunction になります。

console.log((async function(){}).constructor);  // => function AsyncFunction()

とはいえ AsyncFunction が Function を継承しているので、 instanceof は普通に動きます。ご安心ください。

console.log((async function(){}) instanceof Function);  // => true

AsyncFunction コンストラクタ

前項の通り、 AsyncFunction コンストラクタは存在するんだけど、グローバルオブジェクトにはなってません。

const af = new AsyncFunction();  // Exception: ReferenceError: AsyncFunction is not defined

どうしても使いたければ、インスタンスから探っていきます。

const AsyncFunction = (async function(){}).constructor;
const af = new AsyncFunction();

使い方は Function といっしょ。

“async” の読み方

「アシンク」より「エイシンク」くらいっぽいです。まあ日本語で会話してるところにエィスィ↑ンクとか言っても逆に通じないだろうけど。

英単語 “async”, “await” の意味

“async[hronous]” は「非同期」。皆様ご存知ですね。

“await” は「待ち受ける」だそうです。

参考

非同期やるならPromiseでらくらく。(現代的JavaScriptおれおれアドベントカレンダー2017 – 10日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 10日目

概要

メソッドチェインみたいにして、処理を順々に書いていけます。

// Promiseオブジェクトを返す関数
function doLater() {
  return new Promise((resolve, reject) => {
    const result = 123
    setTimeout(() => resolve(result), 1000);
  });
}

doLater()
  .then(result => {
    console.log('(1/2)', result);

    return doLater2();  // Promiseオブジェクトを返す関数
  })
  .then(result => {
    console.log('(2/2)', result);
  })
  .catch(err => {
    console.error(err);
  });

わかっちゃえば簡単だと思うんだけど、慣れないと戸惑うかも。

旧世代: コールバック

非同期処理やるとなると、コールバックを与えて終わったら呼び返してもらう、というのがかつては普通でした。

console.log('Ready.');

doLater(() => {
  console.log('Done.');
});

console.log('Working...');

doLater() が非同期に実行してコールバックするとして、出てくるのは “Ready.” → “Working…” → “Done.” の順になります。

Welcome to the Callback Hell!

まあちょっと使うくらいなら単純で何も問題ないんだけど、これが複数になると、やばい。

console.log('Start!');
doLater1(() => {
  doLater2(() => {
    doLater3(() => {
      doLater4(() => {
        doLater5(() => {
          console.log('Done!');
        });
      });
    });
  });
});

なかなかインデント・ハドウケンって感じで大変な趣き深さがあります。

こんなインデントが厳しくなるようなコールバックに次ぐコールバックを、コールバック地獄(rtヘル)と呼びます。

そしてプロミスへ

Promiseというのを使うと、インデントが深くならずに済みます。

console.log('Start!');
doLater1()
  .then(() => doLater2())
  .then(() => doLater3())
  .then(() => doLater4())
  .then(() => doLater5())
  .then(() => console.log('Done!'));

基本的な使い方

こんな感じ。実行順序にご注意ください。

// 1. `new Promise()` して新しい `Promise` オブジェクトを作成
const p = new Promise((resolve, reject) => {
  // 3. 非同期処理実行
  setTimeout(() => {
    // 4. 完了時に `resolve()` を実行
    const result = 123;
    resolve(result);
  }, 1000);
});

// 2. `Promise` オブジェクトに `then()` でコールバックを登録
p.then((result) => {
  // 5. `resolve()` に与えた情報を伴ってコールバック実行
  console.log(result)
});

前半、1と3、4のところが doLater() になる感じですね。

非同期処理といってもスレッドが分かれているわけではないので、 new Promise() の中で無限ループとかしたら、ブラウザは固まります。

戻り値を返す

まあ戻り値じゃないんだけど、処理をした「結果」として、 resolve() で何かひとつの情報を return することができます。その値は then() で実行されるコールバック関数へ引数として渡されます。

Promise オブジェクト

使い方はまあそこら中に教えてくれるひとがいると思うので、ちょっと仕様的な話の方を。

内部に持っている情報

new Promise() して戻ってくる値は、内部に以下の情報を持っています。

  • 状態。以下のいずれか:
    • pending … 待機
    • fulfilled … 満足(成功)
    • rejected … 拒否(失敗)
  • 結果
  • 満足(成功)時に実行するコールバックのリスト
  • 拒否(失敗)時に実行するコールバックのリスト

executer

new Promise() に与える関数を “executer” と呼ぶみたいです。

このexecuterには二つの引数、関数オブジェクトの resolve と reject が与えられます。

内部で持っている状態はもちろん “pending” から始まり、この resolve() ないし reject() を実行することで変化します。

ちなみに一度変化した後に resolve() 、 rejest() を呼んでも何も起こりません。

コールバック

then() や catch() を使って、コールバックリストへ関数を追加しておくと、状態が変化した際にいずれかのリストが実行されます。

then() で追加しようとした際に既に満足なり拒否なりの状態になっている場合は、その追加しようとしていた関数はすぐ実行されます。

then()

p.then().then()... と繋げることができますが、実は then() 実行のたびに新しい Promise オブジェクトが生成されています。

const p0 = new Promise(resolve => resolve())
const p1 = p0.then(() => undefined)
const p2 = p0.then(() => undefined)
const p3 = p1.then(() => undefined)

console.log(p1 === p0);  // false
console.log(p1 === p2);  // false
console.log(p3 === p1);  // false

複雑なコールバック連携

then() で何か値を return することで、結果として渡す情報を変更することができます。

さらにここで Promise オブジェクトを返すと、その完了(満足なり拒否なり)まで待ってから次へ進むことになります。

// 指定ms待ってから、指定の情報を伴ってコールバック
function sleep(ms, result) {
  console.log(`waiting for ${ms} ms`);
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(result);
    }, ms);
  });
}

// 1000 ms待って、100を返す
sleep(1000, 100)
  .then((result) => {
    // resolve()に与えられた情報を受け取る
    console.log(result);  // 100
  
    // 何もreturnしなければ、
  })
  .then((result) => {
    console.log(result);  // undefined

    return 123;  // 何か値を返すと、
  })
  .then((result) => {
    // 次の仮引数で新しい値の方を受け取れる
    console.log(result);  // 123

    // 新しいPromiseオブジェクトを返すと
    return sleep(1000, 234);
  })
  .then((result) => {
    // Promiseが解決されるまで待ってから、
    // 新しいPromiseの実行結果を得られる
    console.log(result);  // 234
  })

catch()

拒否(失敗)は catch() で拾います。 then() チェインのどこかで失敗した場合は catch() まで飛びます。

Promise.resolve()
  .then(() => {
    console.log(1);
    throw new Error('#1');
  })
  .then(() => {
    console.log(2);
  })
  .catch((error) => {
    console.error(error);
  })
  .then(() => {
    console.log(3);
  });

1 → Error → 3の順にコンソールに出力されます。 2 は、その前にエラーになったので実行されません。

try-catch みたいな感じ。

チェインさせなければ実行される

チェインという表現で良いのかわかんないですけど、前述の通り then() のたびに新しい Promise オブジェクトが生成されます。もし最初のオブジェクトに then() や catch() をぶら下げた場合は、その最初の Promise オブジェクトの結果だけが影響します。

const p0 = Promise.resolve()
p0.then(() => {
  console.log(1);
  throw new Error('#1');
});
p0.then(() => {
  console.log(2);
})
p0.catch((error) => {
  console.error(error);
})
p0.then(() => {
  console.log(3);
});

これなら 1 も 2 も 3 出力され、逆に catch() は 1 のところの例外を拾いません。

成功失敗に依らないコールバック

jQueryの deferred.always() 的なもの、 try-catch の finally 的なものは、ES2017までに存在しません。

代わりに catch() で枝分かれを収束させた後に then() すると、両方の場合に対応できます。

// くるくる表示
showLoading();

// サーバへ情報を送る
data.save()
  .catch(() => {
    // 失敗時の処理
  });
  .then(() => {
    // 成功しても失敗してもくるくる非表示
    hideLoading();
  })

一度catchするのがポイント。

finally()

まだないんだけど、proposalに出てるようです。ES2018で追加されるかも?

Chrome、Firefoxそれぞれ次のバージョンで入るっぽい。

// くるくる表示
showLoading();

// サーバへ情報を送る
data.save()
  .finally(() => {
    // 成功しても失敗しても、くるくる非表示
    hideLoading();
  });

繰り返しますけど、まだないです。

その他

then() の引数

実は第二引数に拒否(失敗)時のコールバックを指定できます。

p.catch(f) は p.then(undefined, f) と等価です。内部でそう実行してます。

resolvedな状態

仕様的には、 reject() すると”rejected” 状態になるのに、 resolve() すると “resolved” じゃなくて “fulfilled” 状態という呼び方になってます。なんでや。

ちなみに “full-” じゃなくて “ful-” 。

fulfilled/rejected 状態の Promise を一発で作る

Promise.resolve() 、 Promise.reject() というメソッドがあり、待機終了した状態のインスタンスを一発で作れます。試験時とかループしながら連結するときとか、ときどき便利。

複数の Promise オブジェクトを制御する

全て完了するまで待つ Promise.all() と、 ひとつでも完了したら進む Promise.race() というのもあります。

参考

更新履歴

  • 2017-12-10 満足(成功)、拒否(失敗)に依らない処理を、併記から catch().then() へ修正(ご指摘頂きました。ありがてえありがてえ)
  • 2017-12-10 「jQueryの “completed” 的なもの」という表現を「jQueryの deferred.alway() 」へ変更
  • 2017-12-10 「 catch() 」を追加
  • 2017-12-10 「チェインさせなければ実行される」を追加
  • 2017-12-10 Promise の静的メソッド各種に言及(ちょっとだけ)

累乗演算子 ** って知ってる?(現代的JavaScriptおれおれアドベントカレンダー2017 – 09日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 09日目

累乗あるいは冪(べき)乗。

概要

** です。

“1024” といえばもちろん2の10乗ですが、そういう計算です。

console.log(2 ** 10);  // => 1024

= とくっついてる版もあり。

var n = 2;
n **= 6;
console.log(n);  // => 64

仕様

基本的にはただの累乗です。

で、細かい例外的なところもちゃんと決まってます。まあ普通の内容なんだけど。

NaN ** 0 は 1

何かの0乗は1になる、と中学校で教わった記憶があるんですが、その原則は NaN にも適用されます。

console.log(2 ** 0);  // 右辺が 0 なら答えは 1
console.log(NaN ** 0);  // 左辺がNaNでも!

NaN にまつわる計算で答えが NaN にならないの、珍しくない?

ちなみに 0 ** NaN は NaN です。

左辺に負数を使う場合

計算順序に注意。カッコつけましょう。

console.log((-2) ** 3);  // => -8
console.log( -2  ** 3);  // SyntaxError

エラーメッセージはこんな感じでした。

  • Chrome: Uncaught SyntaxError: Unexpected token **
  • Edge: Invalid unary operator on the left hand side of exponentiation (**) operator
  • Firefox: SyntaxError: unparenthesized unary expression can’t appear on the left-hand side of ‘**’
  • Safari: SyntaxError: Unexpected token ‘**’. Amiguous unary expression in the left hand side of the exponentiation expression; parenthesis must be used to disambiguate the expression.

Safariなげえ。

ES2016

ES2015 (ES6) じゃなくてその翌年のES2016で追加されました。

その他

Math.pos()

も同じ機能です。

console.log(Math.pow(2, 10));  // => 1024

内部的に ** を実行しているので、 NaN の扱いとかも全部一緒です。

演算子の優先順位

って仕様書でexpressionの定義見ながらじわじわ探す感じ? MDNだと単項演算子より下位にあるんだけど、 -2 ** 2 がエラーになるあたり、同点だったりしない?

ExponentiationExpressionの定義はこう。同じ、だよね?

ExponentiationExpression[Yield]:
    UnaryExpression[?Yield]
    UpdateExpression[?Yield] ** ExponentiationExpression[?Yield]

参考

引数の初期値って便利だよね。(現代的JavaScriptおれおれアドベントカレンダー2017 – 08日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 08日目

概要

= で初期値を設定できます。

いままでこう↓してたのを、

function findRestaurant(type) {
  if (type === undefined) {
    type = 'sushi';
  }

  // ...
}

こう↓書けます。

function findRestaurant(type = 'sushi') {
  // ...
}

初期値が適用される場面

元のコードで条件として if (type === undefined) を示しましたが、新しい書き方でも条件は同じです。

書かなかった場合だけでなく、明示的に undefined を指定した場合も適用されます。

function say(message = 'Yo!') {
  console.log(message);
}

say();  // => "Yo!"
say(undefined);  // => "Yo!"
say(null);  // => null

単純に元の if (type === undefined) の置き換えって感じっすねー。

引数の設定

途中のものだけでも

Javaとかだと初期値って仮引数群の後ろからじゃないと与えられないと思うんですけど、JavaScriptだとそんなことなくて、なんと、途中にだけ付けることができます。

function makeSushi(neta, shari = 'sumeshi', wasabi) {
  console.log(neta, shari, wasabi);
  // ...
}

makeSushi();  // => undefined, "sumeshi", undefined
makeSushi('tuna');  // => "tuna", "sumeshi", undefined
makeSushi('tuna', 'katame', true);  // => "tuna", "katame", true

初期値のないもの wasabi は当然 undefined になります。

これも単純に元の if (type === undefined) の置き換えって感じっすねー。

引数から引数を利用する

左から順に評価されていくので、左側の引数を使った初期値を設定することができます。

function numbers(n = 1, m = n * n, l = n + m) {
  console.log(n, m, l);
}

numbers();  // 1, 1, 2
numbers(3);  // 3, 9, 12
numbers(3, 5);  // 3, 5, 8

うひゃあ。

改めて単純に元の if (type === undefined) の置き換えって感じっすねー。

分割代入と組み合わせる

大変便利な分割代入については別稿参照。

便利だけどなかなか読みづらいので、ご利用は計画的に。

function makeDog({ type = 'shiba' } = { type: 'mix' }) {
  console.log(type);
}

makeDog();  // "mix"
makeDog({});  // "shiba"
makeDog({ type: 'samoyed' });  // "samoyed"

右辺 { type: 'mix' } が引数自体の初期値で、左辺の type = 'shiba' は分割代入時の初期値です。

正直ここちょっとよくわかってないです。

右辺は実行時に評価

関数を実行するとき、 undefined が与えられた際、初期値(右辺)が評価されます。

この例↓だと毎回実行時の時刻になります。

function sayTime(now = new Date()) {
  console.log(`It is ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()} now.`);
}

やっぱり単純に元の if (type === undefined) の置き換えって感じっすねー。

引数未指定時に例外

前項の仕様を使うと、必ず引数を書かせるようにもできます。

function throwNewError(name) {
  throw new Error(name);
}

function sayTime(now = throwNewError('Now is required')) {
  // ...
}

sayTime();  // Exception: Error: Now is required

どこで見かけた話だったかな。例外発生個所が変な場所になっちゃうのであまり実用的ではない気がする。

ちなみに = の右辺に throw は書けないです。

参考

初期値を適用する条件( undefined を与えられたとき)の定義は見つけられませんでした。