技術メモなど

業務や日々のプログラミングのなかで気になったことをメモしています。PHP 成分多め。

HerokuにLaravel+MySQL環境をデプロイする

heroku上にlaravel環境を構築したのでメモ。

前提条件

作業は下記をインストール済の環境で行なっている。また、事前にHerokuへの登録を済ましておく必要がある。

  • php7.1
  • composer1.8.4
  • heroku-cli/6.14.4 (darwin-x64) node-v12.1.0
  • git2.9.2

laravelプロジェクトの作成

以下コマンドでlaravelプロジェクトを作成する。myAppには任意のディレクトリ名を指定する。

$ composer create-project laravel/laravel myApp

herokuアプリの作成

作成したディレクトリに移動し、herokuアプリを作成する。myAppには任意のアプリ名を指定する。

$ cd myApp
$ heroku login
$ heroku create myApp --buildpack heroku/php

これでheroku上にアプリとgitリポジトリが作成される。 ex) https://myApp.herokuapp.com/ | https://git.heroku.com/myApp.git

gitの登録

herokuのgitリモートリポジトリが利用できるようになったので、ローカルにもgitを作成する。

$ git init
$ heroku git:remote -a myApp // リモートリポジトリを登録

これでHeroku上のgitリポジトリソースコードをpushできるようになる。 laravelプロジェクトをpushしておく。

$ git add -A .
$ git commit -m "first commit"
$ git push heroku master

clearDBの作成

続いてデータベースを利用できるようにする。 herokuに用意されているMySQL互換のclearDBアドオンを使用する。

heroku addons:add cleardb

登録が成功すると、heroku configコマンドから作成したデータベースのURLを確認できる。

heroku config
CLEARDB_DATABASE_URL: mysql://[ユーザー名]:[パスワード]@[ホスト名]/[データベース名]?reconnect=true```

herokuに作成したデータベースの設定を行う。先ほど確認したURLを元に、以下を設定する。

$ heroku config:set DB_DATABASE=[データベース名]
$ heroku config:set DB_HOST=[ホスト名]
$ heroku config:set DB_USERNAME=[ユーザー名]
$ heroku config:set DB_PASSWORD=[パスワード]

なお、作成したデータベースにはmysqlコマンドから接続できる。

$ mysql -u [ユーザー名] -h [ホスト名]

マイグレーションの実行

clearDBはデフォルトはMySQL5.6互換であるため、AppServiceProviderにデフォルトのインデックス用文字列長の設定を行う必要がある。 (参照:インデックス長とMySQL/MariaDB)

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema; // 追加

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Schema::defaultStringLength(191); // 追加
    }
}

上記変更をpushした後、migrateを実行できるようになる。

$ git add -A .
$ git commit -m "デフォルトのインデックス用文字列長を191に変更"
$ git push heroku master
$ heroku run php artisan migrate

Procfile 作成

herokuに、アプリを起動するために実行すべきコマンド及びアプリケーションのドキュメントルートを設定する。
設定はProcfileを作成し、そのなかに記述する。

$ touch Procfile

Procfileに下記を追加する(webサーバにnginxを、ドキュメントルートにpublic/を指定している)。

web: vendor/bin/heroku-php-nginx public/

作成が完了したらpushしておく。

$ git add -A .
$ git commit -m "Procfile"
$ git push heroku master

APP_KEYの設定

APP_KEYが必要なのでherokuに設定しておく。

heroku config:set APP_KEY=$(php artisan key:generate --show)

ここまで設定したら、laravelの初期画面が表示されるようになる。

heroku open

参考文献

Vuexのstate/getters/mutations/actionsについて調べた

JavaScript関連の基礎学習なう。
今回はVuexのステート・ゲッター・ミューテーション・アクションについて調べた。

ストアインスタンスの生成

Vuexを使用するには、まずストアオブジェクトを作成し、Vueインスタンスに登録する。
ストアオブジェクトの状態は、stateプロパティで持つことができる。
登録されたストアオブジェクトは、this.$storeを介してVueインスタンス内のすべてのコンポーネントから参照できる。

// main.js
import Vue from 'vue'
import Vuex from 'vuex'
import App from './components/App.vue';

// Vuexの使用を宣言
Vue.use(Vuex);

// ストアインスタンスの生成
const store = new Vuex.Store({
  // 状態はstateプロパティで保持する
  state: {
    count: 1,
  }
})

new Vue({
  // Vueに作成したストアインスタンスを登録する。
  store,
  render: h => h(App),
}).$mount('#app')
// App.vue
<template>
  <div>
    {{ count }}
  </div>
</template>

<script>

export default {
  computed: {
    count () {
      return this.$store.state.count // 1
    }
  }
}
</script>

ストアオブジェクトを使っている状態でも、本来のdataプロパティはローカルステートとして利用できる。

// App.vue
<template>
  <div>
    {{ storeCount }}
    {{ localCount }}
  </div>
</template>

<script>

export default {
  data: function(){
    return {
      count: 11,
    }
  },
  computed: {
    storeCount () {
      return `store count: ${this.$store.state.count}` // store count: 1
    },
    localCount () {
      return `local count: ${this.count}` // local count: 11
    },
  }
}
</script>

getters

コンポーネントで共通のcomputedメソッドがあるとき、共通化の手段としてgettersオプションが提供されている。 gettersに登録された関数の結果はキャッシュされ、その関数が依存しているstateの値が変更されたときにのみ再評価される。

定義したゲッターは、$store.gettersからアクセスできる(プロパティスタイルアクセス)。
また、ゲッターの戻り値を関数にすることで、ゲッター自身に引数を渡せるようになる(メソッドスタイルアクセス)。ただし、メソッドスタイルアクセスのゲッターについては結果はキャッシュされず、実行のたびに計算が行われる。

// main.js
import Vue from 'vue'
import Vuex from 'vuex'
import App from './components/App.vue';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 1,
  },
  // getterの定義
  getters: {
    // stateオブジェクトには、第一引数からアクセスできる
    storeCount (state) {
      return `store count: ${state.count}`
    },
    // 関数を戻り値にすることで、getterに引数を渡せるようになる。
    totalCount(state) {
      return function(localCount){
        return `total count: ${state.count + localCount}`
      };
    }
  }
})

new Vue({
  store,
  render: h => h(App),
}).$mount('#app')
// App.js
<template>
  <div>
    {{ storeCount }}
    {{ localCount }}
    {{ totalCount }}
  </div>
</template>

<script>

export default {
  data: function(){
    return {
      count: 11,
    }
  },
  computed: {
    storeCount () {
      return this.$store.getters.storeCount // store count: 1
    },
    localCount () {
      return `local count: ${this.count}` // local count: 11
    },
    totalCount () {
      return this.$store.getters.totalCount(this.count) // total count: 1
    },

  }
}
</script>

mutations

ストアの状態は、ミューテーションによってのみ変更される。 ミューテーションはタイプハンドラを持ち、ハンドラ関数は直接実行することができない。 実行するためには、store.commitをタイプを指定して呼び出す必要がある。 また、ハンドラ関数は非同期処理を行なってはならない

// main.js
import Vue from 'vue'
import Vuex from 'vuex'
import App from './components/App.vue';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 1,
  },
  getters: {
    storeCount (state) {
      return `store count: ${state.count}`
    },
    totalCount(state) {
      return function(localCount){
        return `total count: ${state.count + localCount}`
      };
    }
  },
  // ミューテーションの定義
  mutations:{
    // stateオブジェクトには、第一引数からアクセスできる
    // 第二引数から、commitに渡された値を受け取れる。
    increment(state, payload){
      state.count += payload.num;
    }
  }
})

new Vue({
  store,
  render: h => h(App),
}).$mount('#app')
// App.vue
<template>
  <div>
    {{ storeCount }}
    {{ localCount }}
    {{ totalCount }}
    <input type="button" @click="storeCountIncrement" value="increment">
  </div>
</template>

<script>

export default {
  data: function(){
    return {
      count: 11,
    }
  },
  computed: {
    storeCount () {
      return this.$store.getters.storeCount // store count: 1
    },
    localCount () {
      return `local count: ${this.count}` // local count: 11
    },
    totalCount () {
      return this.$store.getters.totalCount(this.count) // total count: 1
    },

  },
  methods: {
    storeCountIncrement(){
      // 第一引数にタイプを指定する。引数が必要な場合は第二引数に指定する。
      this.$store.commit('increment', {num: 1});

    //   commitには以下のように、オブジェクトを渡すこともできる
    //   this.$store.commit({
    //     type: 'increment',
    //     num: 1
    //   });
    }
  }
}
</script>

actions

アクションもミューテーションと同じくタイプハンドラを持ち、ハンドラ関数の実行にはstore.dispatchをタイプを指定して呼び出す。
ミューテーションとの違いは以下。 - 自身は状態を変更しない。変更するにはミューテーションをコミットする。 - 任意の非同期処理を含むことができる。

// main.js
import Vue from 'vue'
import Vuex from 'vuex'
import App from './components/App.vue';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 1,
  },
  getters: {
    storeCount (state) {
      return `store count: ${state.count}`
    },
    totalCount(state) {
      return function(localCount){
        return `total count: ${state.count + localCount}`
      };
    }
  },
  mutations:{
    increment(state, payload){
      state.count += payload.num;
    }
  },
  actions: {
    // 第一引数にはコンテクストオブジェクト(後述)が渡され、ここからcommitできる。
    // 第二引数から、dispatchに渡された値を受け取れる。
    increment(context, payload){
      context.commit('increment', payload);
    }
  }
})

new Vue({
  store,
  render: h => h(App),
}).$mount('#app')
<template>
  <div>
    {{ storeCount }}
    {{ localCount }}
    {{ totalCount }}
    <input type="button" @click="storeCountIncrement" value="increment">
  </div>
</template>

<script>

export default {
  data: function(){
    return {
      count: 11,
    }
  },
  computed: {
    storeCount () {
      return this.$store.getters.storeCount // store count: 1
    },
    localCount () {
      return `local count: ${this.count}` // local count: 11
    },
    totalCount () {
      return this.$store.getters.totalCount(this.count) // total count: 1
    },

  },
  methods: {
    storeCountIncrement(){
     // 第一引数にタイプを指定する。引数が必要な場合は第二引数に指定する。
      this.$store.dispatch('increment', {num: 1});

    //   commitと同じく、オブジェクトを渡すこともできる
    //   this.$store.dispatch({
    //     type: 'increment',
    //     num: 1
    //   });
    }
  }
}
</script>

本文中のコードは以下にまとめてあります。 Vuex調査

参考文献

Vuex - ステート
Vuex - ゲッター
Vuex - ミューテーション
Vuex - アクション

JavaScriptのexport/importについて調べた

JavaScript再入門中。 今週はexport/importについてメモ。

export/importとは

export 文は、モジュールから関数、オブジェクト、プリミティブな値をエクスポートするための JavaScript モジュールを作成するときに使用します。これらは別のプログラムで、 import 文で使用できます。 エクスポートされたモジュールは宣言のあるなしにかかわらず strict mode で動作します。エクスポート文は、埋め込まれたスクリプトでは使えません。
export - MDN

import 文は、他のモジュールからエクスポートされたバインディング(関数、オブジェクト、プリミティブ)をインポートするために用います。インポートされたモジュールは宣言のあるなしにかかわらずStrict モードで動作します。import 文は、type="module" を指定しない限り、埋め込まれたスクリプトでは使えません。
import - MDN

exportキーワードは、ファイルの外部からアクセス可能であるべき変数や関数にラベル付けをする。
importキーワードは、exportされたものを読み込む。

エクスポートには、名前付きエクスポート(named exports)デフォルトエクスポート(default exports)の2種類がある。
名前付きエクスポートはモジュールごとに複数持つことができるが、デフォルトエクスポートは1つしか持てない。

// 関数helloをexport
export function hello(str) {
  console.log( `Hello, ${str}!` );
} // セミコロンはつけない(関数式にならないよう注意)

// 配列fooのexport
export let foo = ['bar', 'baz'];

// 定数Hogeのexport
export const Hoge = 'hoge';

// クラスUserのexport
export class User {
  constructor(name) {
    this.name = name;
  }
}

// デフォルトエクスポート
export default function(){
  console.log('default exports!');
}
// 関数helloをimport
import { hello } from "./sample.js";
hello('World'); //Hello World!

// 配列fooをimport
import { foo } from "./sample.js";
console.log(foo); //["bar", "baz"]

// 定数Hogeをimport
import { Hoge } from "./sample.js";
console.log(Hoge); //hoge

// クラスUserをimport
import { User } from "./sample.js";
console.log( (new User('tarou')).name ); //tarou

// デフォルトエクスポートを変数Sampleにインポート
import Sample  from "./sample.js";
Sample(); // default exports!

named exports

名前付きエクスポートをimportで読み込む際は変数名を波括弧で囲む必要があり、別名の設定や、複数同時指定などもできる。 また * as 変数名で一括インポートできる。

// 別名の設定
import { Hoge as Fuga }from "./sample.js";
console.log( Fuga ); //hoge

// 複数同時にimport
import {
    foo, 
    User as Tarou
} from "./sample.js";
console.log(foo); //['bar', 'baz']
console.log( (new Tarou('tarou').name) ); //tarou

// 一括import。すべての名前付きエクスポートをLibに読み込む
import * as Lib  from "./sample.js";
// プロパティ名でそれぞれのモジュールにアクセスできる
Lib.hello('World'); //Hello, World!
console.log( Lib.foo ); //['bar', 'baz']

default exports

デフォルトエクスポートは、defaultキーワードを付与するか、 変数名 as defaultで定義する。 また、変数名を必要とせず、無名関数等でもエクスポートできる。

// 無名関数でもエクスポート可(デフォルトエクスポートは1モジュールにつき1つであるため)
export default function (){ /*...*/ }

//同一モジュールに複数 default が定義されているとエラー
export default class{
    constructor() { ... }
}
//SyntaxError: Duplicate export of 'default'
// デフォルトエクスポート
let Hoge = "default exports";

// 以下は export default Hoge と等価
export { Hoge as default };

// 名前付きエクスポート
let Foo = "named exports";
export { Foo };

importで読み込む際に波括弧を指定しなかった場合、デフォルトエクスポートが読み込まれる。 また、defaultキーワードを使用することで別名をつけたり名前付きエクスポートと同時にimportしたりできる。

// 波括弧をつけないとデフォルトエクスポートがインポートされる
import Hoge from "./sample_2.js";
console.log( Hoge ); //default exports

// defaultキーワードの別名としてインポート
import { default as Fuga }from "./sample_2.js";
console.log( Fuga ); //default exports

// 名前付きと同時にimport
import {
    default as Piyo, 
    Foo
} from "./sample_2.js";
console.log(Piyo); //default exports
console.log( Foo );  //named exports

// 一括importした場合、defaultプロパティにデフォルトエクスポートが入っている
import * as Lib  from "./sample_2.js";
console.log( Lib.default); //default exports
console.log( Lib.Foo ); //named exports

import() 関数

importキーワードにはいくつかの制約がある。

  • ブロックの中に置くことはできない
  • モジュールパスを変数などで動的に生成することはできない
// ブロックのなかに置くことはできない
if(true){
    import { hello } from "./sample.js"; // Uncaught SyntaxError: Unexpected token {
}

// パスにプリミティブな文字列以外を使用することはできない。
let path = "./sample.js";
import { hello } from path; // Uncaught SyntaxError: Unexpected identifier

動的にimportする必要がある場合、import()関数を使用する。 import()関数はPromiseオブジェクトを返却するただの関数であるため、importキーワードのような制約を受けずに使用できる。

let path = "./sample.js";
import(path)
    .then((obj) => {
        // 各モジュールには、引数objのプロパティからアクセスできる。
        obj.hello('World');
        obj.default();
    });

本文中のコードは以下にまとめてあります。 export/import調査

参考文献

JavaScriptのasync/waitについて調べた

JavaScript再入門中。 今回はasync/waitについてメモ。

async/waitとは

async function 宣言は、 AsyncFunction オブジェクトを返す 非同期関数 を定義します。非同期関数は非同期でイベントループを介して実行され、暗黙的にPromiseを返します。なおコードのシンタックス及び構造は通常の同期関数と非常に似たものになります。
async function - MDN

await 演算子は、async function によって Promise が返されるのを待機するために使用します。
wait - MDN

async/awaitを利用することで、Promise処理をより簡潔に書くことができる。

async function

async functionは、関数定義の前にasyncキーワードをつけて宣言する。

// async functionの定義
var asyncFunc = async function () {
  return 1;
}

// async functionは実行するとPromiseオブジェクトを返却する
asyncFunc().then( console.log );
// 1

async関数は、returnで終了した場合はresolveされた、throwで終了した場合はrejectされたPromiseオブジェクトを返却する。 また、自前で作成したPromiseオブジェクトを返却することもできる。 以上から、async関数の戻り値はPromiseオブジェクトであることが保証される。

var asyncFunc1 = async () =>{ return 1 };
asyncFunc1()
    .then( v => console.log(`then: ${v}`) )
    .catch( v => console.log(`catch: ${v}`) );
// then: 1

var asyncFunc2 = async () =>{ throw new Error('error')};
asyncFunc2()
    .then( v => console.log(`then: ${v}`) )
    .catch( v => console.log(`catch: ${v}`) );
// catch: Error: error

// returnを明示しなかった場合はresolveになる
var asyncFunc3 = async () =>{ };
asyncFunc3()
    .then( v => console.log(`then: ${v}`) )
    .catch( v => console.log(`catch: ${v}`) );
// then: undefined

// 明示的にPromiseオブジェクトを返却することもできる
var asyncFunc4 = async () => Promise.resolve(4);
asyncFunc4()
    .then( v => console.log(`then: ${v}`) )
    .catch( v => console.log(`catch: ${v}`) );
// then: 4

var asyncFunc5 = async () => Promise.reject(5);
asyncFunc5()
    .then( v => console.log(`then: ${v}`) )
    .catch( v => console.log(`catch: ${v}`) );
// catch: 5

await

awaitキーワードは、async関数の実行を一時停止させ、Promiseの解決または拒否を待機する。

var asyncFunc = async () => {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("success!"), 1000)
  });

  let result = await promise; // promise が解決するまで待機

  console.log(result);
}

asyncFunc();
// success!

awaitキーワードは、async関数以外の箇所では宣言できない。
グローバルや、通常の関数内部で実行するとシンタックスエラーが発生する。

// 通常の関数内部では使用できない
var func = function(){
    let promise  = await new Promise((resolve, reject) => {});
    await promise; // Error: await is only valid in async function
}

awaitに渡されたPromiserejectされた場合、通常の例外のように値がスローされる。 スローされた値は、例外のようにtry..catchでキャッチできる。 また、Promise.catchメソッドで処理することもできる。

// try..catch構文を使って処理
var asyncFunc1 = async () => {
  try {
    var foo = await Promise.reject(new Error('failed..')); // Errorオブジェクトがスローされる
  } catch (e) {
    console.log(e);
  }
}
asyncFunc1();
// Error: failed..

// catchメソッドを使っても処理できる
var asyncFunc2 = async () => {
    var z = await Promise.reject(new Error('failed..')).catch(console.log);
}
asyncFunc2();
// Error: failed..

記事中のコードは以下にまとめてあります。
async/wait調査 · GitHub

参考文献

JavaScriptのPromiseについて調べた

JavaScript再入門中async/awaitを覚えるにあたってPromiseをおさらいしないといけなさそうだったのでメモ。

Promiseとは

Promiseは非同期処理の最終的な完了もしくは失敗を表すオブジェクトです。 Promiseを使う - MDN

// 構文
new Promise( function(resolve, reject) { ... } );

Promiseの状態

生成されたPromiseオブジェクトは、以下の状態を持つ。

  • pending: 初期状態。成功も失敗もしていない。
  • fulfilled: 処理が成功して完了した。
  • rejected: 処理が失敗した。

Promiseコンストラクタに与えられた関数(executor関数)には、2つの引数resolve関数とreject関数が渡される。
executor関数は内部でなんらかの非同期処理を実行し、 成功した場合にはresolve関数を呼び出すことでfulfilledに、 失敗した場合にはreject関数を呼び出すことでrejectedPromiseオブジェクトの状態を変更できる。
また、executor関数で例外がスローされた場合、そのPromiseオブジェクトの状態はrejectedとなる。

var promise1 = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", 'http://example.com');
    xhr.onload = () => resolve(xhr.status); // resolveは成功を表す
    xhr.onerror = () => reject(xhr.status); // rejectは失敗を表す
    xhr.send();
});


var promise2 = new Promise((resolve, reject) => {
    throw new Error('error!'); // 例外がスローされた場合はrejected
});

then / catch / finally

生成したPromiseオブジェクトは、インスタンスメソッドthenを使って成功時/失敗時の処理を登録することができる。
Promiseオブジェクトが成功(fulfilled)時には第一引数の関数が、失敗(rejected)時には第二引数の関数が実行される。
また、thenの他に、失敗時の処理のみを登録できるcatchメソッド、Promiseオブジェクトの成功/失敗に関わらず必ず実行されるfinallyメソッドがある。

promise.then(
    function(value){ 
        /*
            成功時の処理
            引数valueには、reslve()に渡された値が入っている
        */ 
    }, 
    function(reason){
        /*
            失敗時時の処理
            引数reasonには、reject()またはError()に渡された値が入っている
        */
    }
);

promise.catch( function(reason){ /* 失敗時時の処理 */} );

//catchを使った書き方は、以下の書き方と等価
promise.then(
    undefind,
    function(reason){ /* 失敗時時の処理 */}
);

promise.finally(function(){
    /* 
        成功/失敗共通の処理
        finallyメソッドは引数をとらない。
    */
});

Promiseチェーン

実行されたthen/catch/finallyメソッドは、新しくPromiseオブジェクトを返す。 そのため、メソッドチェーンのように書くことができる。

var promise = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve(0);
  }, 300);
});

promise
  .then( value =>{
    console.log(value);
    // コールバック関数内でreturnすることで、値を後続の処理に引き渡せる。
    return ++value;
  })
  .then( value =>{
    console.log(value);
    return ++value;
  })
  .then( value =>{
    console.log(value);
    return ++value;
  })
;
// 出力は以下のようになる
// 0
// 1
// 2

なお、メソッドチェーンの途中で例外が発生した場合、その処理は中断され、後続の最も近いcatchメソッドもしくはthenのエラー処理関数が実行される。

var promise = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve(0);
  }, 300);
});

// catchによる例外処理
promise
  .then( value =>{
    console.log(value);
    return ++value;
  })
  .then( value =>{
    // 例外をスロー
    throw new Error('Error!');
  })
  .then( value =>{
    // 前の処理で例外がスローされているため、この処理は実行されない
    console.log(value);
    return ++value;
  })
  .catch(error => {
    console.log(error);
  })
;
// 出力は以下のようになる
// 0
// Error: Error!
var promise = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve(0);
  }, 300);
});

// thenのエラー処理関数による例外処理
promise
  .then( value =>{
    console.log(value);
    return ++value;
  })
  .then( value =>{
    // 例外をスロー
    throw new Error('Error!');
  },
  error =>{
    // 同じthenメソッドに登録されているエラー処理関数は実行されない。
    console.log(`then1:  ${error}`);
  })
  .then( value =>{
    // 前の処理で例外がスローされているため、この処理は実行されない
    console.log(value);
    return ++value;
  },
  error =>{
    console.log(`then2:  ${error}`);
  })
  .catch(error => {
    // 前処理のthenで例外が処理されているため、この処理は実行されない。
    console.log(`catch:  ${error}`);
  })
;
// 出力は以下のようになる
// 0
// then:  Error: Error!

Promise.resolve と Promise.reject

Promiseオブジェクトの生成にはnew Promise()の他にもシンタックスシュガーとしてPromise.resolveおよびPromise.rejectという静的メソッドが用意されている。

Promise.resolveは成功状態の、Promise.rejectは失敗状態のPromiseオブジェクトを返却する。

Promise.resolve('success');

// 上記は以下と等価
new Promise(function(resolve){
    resolve('success');
});

Promise.reject(new Error('error!'));

// 上記は以下と等価
new Promise(function(resolve,reject){
    reject(new Error('error!'));
});

Promise.all と Promise.race

複数のPromiseオブジェクトをまとめて処理する方法として、Promise.allPromise.raceがある。

Promise.allは引数にPromiseオブジェクトの配列を取り、すべてのオブジェクトが成功状態のとき後続のthenの成功時処理を実行する。ひとつでも失敗するとthenは実行されず、catchまたはthenの失敗処理が実行される。

Promise.all([
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.resolve(3),
])
.then(values =>{
  console.log(values[0]);
  console.log(values[1]);
  console.log(values[2]);
});
// 出力は以下
// 1
// 2
// 3

Promise.all([
  Promise.resolve(1),
  Promise.reject(new Error('error')),
  Promise.resolve(3),
])
.then(values =>{
  console.log(values[0]);
  console.log(values[1]);
  console.log(values[2]);
})
.catch(error =>{
  console.log(error);
});
// 出力は以下
// Error: error

Promise.raceもまた引数にPromiseオブジェクトの配列を取り、どれか一つでも成功または失敗の状態になったら後続の処理を実行する。

var promise = Promise.race([
    new Promise(function (resolve, reject) {
        const xhr = new XMLHttpRequest();
        xhr.open("GET", 'http://example.com');
        xhr.onload = () => resolve(xhr.status);
        xhr.onerror = () => reject(xhr.status);
        xhr.send();
    }),
    new Promise(function (resolve, reject) {
         setTimeout(() => reject(new Error('request timeout')), 5000)
    })
]);
      
promise
  .then(value => console.log(value))
  .catch(error => console.log(error));

なお、後続の処理に引き渡されるのは最初に解決したPromiseオブジェクトの結果のみだが、他の処理が中断されるわけではないので注意が必要。

var promise = Promise.race([
    new Promise(function (resolve, reject) {
        console.log('promise 1');
        setTimeout(() => reject(new Error('request timeout')), 5000)
    }),
    new Promise(function (resolve, reject) {
        console.log('promise 2');
        setTimeout(() => reject(new Error('request timeout')), 1000)
    })
]);
      
promise
  .then(value => console.log(value))
  .catch(error => console.log(error));
// 出力は以下
// promise 1
// promise 2
// Error: request timeout

記事中のソースは以下にまとめてあります。

Promise調査 · GitHub

参考文献

Promiseを使う - JavaScript | MDN

Promise - JavaScript | MDN

JavaScript Promiseの本

JavaScriptのアロー関数について調べた

数年振りにJavaScriptを触ることになりそうなので、改めて今時の仕様をきちんと理解したいと思いいろいろ調べている。 今回はアロー関数について調べたのでメモ。

アロー関数とは

アロー関数式は、より短く記述できる、通常の function 式の代替構文です。また、this, arguments, super, new.target を束縛しません。アロー関数式は、メソッドでない関数に最適で、コンストラクタとして使うことはできません。
アロー関数 - MDN

// 通常のfunction式による関数定義
var hoge = function(){
    console.log('hoge');
};

// アロー関数式を使った定義
var hoge = () => {
    console.log('hoge');
};

短縮構文

アロー関数では、引数の()やブロックの{}を省略できる場合がある。

// 引数がひとつの場合、()を省略できる。
var hoge = arg => { return arg }; 
console.log(hoge(1)); // 1

// 引数が複数の場合、()は省略できない。
var hoge = arg1, arg2 => { return arg1 + arg2 }; 
console.log(hoge(1)); // SyntaxError: Unexpected token =>

// 本体が単一の式の場合、{}およびreturnを省略できる()。
var hoge = arg =>  arg + 1; 
console.log(hoge(1)); // 2

// 戻り値がオブジェクトリテラルの場合、()を付与する必要がある。
var hoge = arg => ({foo: arg}); 
console.log(hoge(1)); // { foo: 1}

// 本体が複数の式の場合、{}は省略できない。
var hoge = arg =>  arg + 1; arg + 2;
console.log(hoge(1)); // ReferenceError: arg is not defined

this

通常の関数は、関数ごとに自身のthisを定義するが、 アロー関数式のthisは、自身のthisを持たず、レキシカルスコープのthisを使用する。 つまり変数探索ルールと同じような挙動をする(現在のスコープ内にthisがない場合、外側のスコープを探索し、最終的にグローバルスコープに到る)。

// function式
const tarou = {
    family_name: '山田',
    first_name: '太郎',
    fullname: function(){
        console.log(`${this.family_name} ${this.first_name}`);  
    }
};
tarou.fullname(); //山田 太郎

// アロー関数式
const hanako = {
    family_name: '山田',
    first_name: '花子',
    fullname: () =>{
        console.log(`${this.family_name} ${this.first_name}`);  
    }
};
// hanako.fullname()内のthisは外側のthis(=グローバルオブジェクト)を
// 参照しているため、undefinedが返る。
console.log(hanako.fullname()); //undefined undefined

コールバック関数を利用する場合、従来のfunction式ではthisがグローバルオブジェクトを参照するため、呼び出し元のthisを参照したい場合var self = thisのように変数に一時代入しなければならなかったが、アロー関数式は実行時のスコープ内のthisを参照するためその必要がなくなった。

const hoge = {
    count: 0,
    // コールバック関数に通常の関数を渡す
    func: function(){
        setTimeout(function(){
            // このthisはグローバルオブジェクトを参照している
            this.count++;
            console.log(this.count);
        }, 1000)  
    },
    // コールバック関数にアロー関数を渡す
    arrowfunc: function(){
        setTimeout(()=>{
            // このthisは外側のthis(=hoge)を参照している
            this.count++;
            console.log(this.count);
        }, 1000)   
    }
};
hoge.func(); // NaN
hoge.arrowfunc(); // 1

参考文献

アロー関数 - MDN

PHPでMarkdownをHTMLに変換するparsedownを試した

Markdownをパースするライブラリを調べたところparsedownが良い感じだったのでメモ。 文中のPHPは7.2を使用。

準備

composerでインストール。

$ composer require erusev/parsedown

実際に変換してみる

<?php
require_once "./vendor/autoload.php";

$Parsedown = new Parsedown();
// safeModeをtrueにしておくとエスケープしてくれる。
$Parsedown->setSafeMode(true);

$md = <<<EOF
# 見出し1
parsedownのサンプルです。   
parsedownのサンプルです。   
parsedownのサンプルです。


* 箇条書き
* 箇条書き

*太文字*

|題名|題名|題名|
|:---|:---:|---:|
|左寄せ|中央寄せ|右寄せ|

<script>
alert('hoge');
</script>
EOF;

// 出力
echo $Parsedown->text($md);

上記の例だと、以下のようなhtmlが出力される。

<h1>見出し1</h1>
<p>parsedownのサンプルです。<br />
parsedownのサンプルです。<br />
parsedownのサンプルです。</p>
<ul>
<li>箇条書き</li>
<li>箇条書き</li>
</ul>
<p><em>太文字</em></p>
<table>
<thead>
<tr>
<th style="text-align: left;">題名</th>
<th style="text-align: center;">題名</th>
<th style="text-align: right;">題名</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">左寄せ</td>
<td style="text-align: center;">中央寄せ</td>
<td style="text-align: right;">右寄せ</td>
</tr>
</tbody>
</table>
<p>&lt;script&gt;
alert('hoge');
&lt;/script&gt;</p>

ブラウザでも確認。

f:id:shkn:20190506023141p:plain
sample.php

簡単にMarkdown->htmlの変換に対応することができた。

参考文献

本家(デモ有)
Better Markdown Parser in PHP

Github
GitHub - erusev/parsedown: Better Markdown Parser in PHP