かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript (ES2015) 今更のPromise

JavaScriptを扱うにあたって避けては通れない道、Ajaxとかタイマーとかの非同期処理…
ES7のawait/asyncを学ぶ方が良いのでしょうが、jQuery.deferred辺りで止まってしまっていると、いきなりawait/asyncは辛いので、Promiseをざっくり理解してawait/asyncに置き換える方法で学習するのが良いかと思い今更のPromise入門です。

Promiseの基本

new Promise(function(resolve, reject) {...})

  • resolve ... 処理が成功したら呼び出す関数
  • reject ... 処理が失敗したら呼び出す関数

 

function hi(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(name) {
        resolve(`Hi, ${name}!`)
      } else {
        reject('ERROR name is empty!')
      }
    }, 100)
  })
}

.then()

Promise.prototype.then(successCallback, failureCallback)

返されるPromiseオブジェクトに.thenをメソッドチェーンさせることで、Promiseからresolve / rejectが呼び出されるまで待機して値を受け取れる

  • successCallback ... Promise.resolveが実行された時に呼び出される関数 (成功時に呼ばれる関数)
  • failureCallback ... Promise.rejectが実行された時に呼び出される関数 (失敗時に呼ばれる関数)
function onSuccess(res) {
  console.log('onSuccess', res)
}
function onReject(error) {
  console.log('onReject', error)
}

hi('星宮いちご').then(onSuccess, onReject)
// => onSuccess Hi, 星宮いちご!
hi().then(onSuccess, onReject)
// => onReject ERROR name is empty!

※ 非同期処理を同期的に実行したいときネストが深くなってしまう

hi('星宮いちご').then((res1) => {
  console.log('onSuccess 1', res1)
  hi('霧矢あおい').then((res2) => {
    console.log('onSuccess 2', res2)
    hi('紫吹蘭').then((res3)=> {
      console.log('onSuccess 3', res3)
    }, onReject)
  }, onReject)
}, onReject)
// => onSuccess 1 Hi, 星宮いちご!
// => onSuccess 2 Hi, 霧矢あおい!
// => onSuccess 3 Hi, 紫吹蘭!

thenのコールバック内でPromiseを返す関数をreturnすれば、Promise.thenをメソッドチェーンにすることができる

hi('星宮いちご').then((res1) => {
  console.log('onSuccess 1', res1)
  return hi('霧矢あおい')
})
.then((res2) => {
  console.log('onSuccess 2', res2)
  return hi('紫吹蘭')
})
.then((res3) => {
  console.log('onSuccess 3', res3)
})
// => onSuccess 1 Hi, 星宮いちご!
// => onSuccess 2 Hi, 霧矢あおい!
// => onSuccess 3 Hi, 紫吹蘭!

※ ただしメソッドチェーンしたthen内のコールバックはスコープが異なるので、前のthenのコールバックで受け取った値を直接参照することはできない

hi('星宮いちご').then((res1) => {
  return hi('霧矢あおい')
})
.then((res2) => {
  coneolse.log(res1)
  // => ReferenceError: res1 is not defined
})

※ エラーになった場合の処理が記述されてない場合、Promise.reject()が発生するとワーニングになってしまう。

hi()
.then((res1) => {
  console.log('res1')
  return hi()
})
.then((res2) => {
  console.log('Promise Complete')
})
// => UnhandledPromiseRejectionWarning: Unhandled promise rejection
// => DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

.catch()

Promise.prptpyupe.catch(onRejected)

Promiseが失敗した時に実行される節
エラーになった箇所以降のthenは実行されない

hi('神前美月').then((res1) => {
  console.log('onSuccess 1', res1)
  return hi() // Promise.rejectになる
})
.then((res2) => {
  console.log('onSuccess 2', res2)
  return hi('藤堂ユリカ')
})
.then((res3) => {
  console.log('onSuccess 3', res3)
})
.catch((error) => {
  // 共通のエラー処理
  console.log('Catch Error:', error)
})
// => onSuccess 1 Hi, 神前美月!
// => Catch Error: ERROR name is empty!

非同期処理を同期的に実行する時、特別なことがなければエラーはcatchで取るのが良さそう。

Promise.all

Promise.all() ... 引数で渡された全ての処理が完了したら結果の配列が返される。実行する処理に1つでもエラーがあるとエラーになる。

Promise.all([
  hi('神前美月'),
  hi('藤堂ユリカ'),
  hi('一ノ瀬かえで')
])
.then((res) => {
  // すべての非同期処理が完了したら、結果がArrayで返される
  console.log(res)
})
.catch((error) => {
  console.log(error)
})
// => [ 'Hi, 神前美月!', 'Hi, 藤堂ユリカ!', 'Hi, 一ノ瀬かえで!' ]

1つでもエラーがあると、最初にエラーになったものが返る

const task1 = new Promise((resolve, reject) => setTimeout(resolve, 50, 'TASK1'))
const task2 = new Promise((resolve, reject) => setTimeout(reject, 200, 'TASK2'))
const task3 = new Promise((resolve, reject) => setTimeout(reject, 100, 'TASK3'))

Promise.all([
  task1,
  task2,
  task3
])
.then((res) => console.log(res))
.catch((error) => console.log(error))
// => TASK3

Promise.race

Promise.race() ... 引数で渡された処理のいずれか1つが完了したら、完了した処理の値が返される。完了した最初の処理がエラーならエラー処理に流れる。※ 成功するものがあるか待つわけではない。

const task1 = new Promise((resolve, reject) => setTimeout(resolve, 100, '音城セイラ'))
const task2 = new Promise((resolve, reject) => setTimeout(resolve, 50, '冴草きい'))
const task3 = new Promise((resolve, reject) => setTimeout(resolve, 10, '風沢そら'))

Promise.race([
  task1,
  task2,
  task3,
])
.then((res) => {
 // 最初に完了した処理の返り値がそのまま渡される
 console.log(res)
})
.catch((error) => {
  console.log(error)
})
// => 風沢そら

最初に返されるものがエラー(reject)ならcatchに、並列処理中にエラーになるものがあっても、最初に返るのが正常な値(resolve)ならthenに処理が流れる

const task1 = new Promise((resolve, reject) => setTimeout(resolve, 100, '大空あかり'))
const task2 = new Promise((resolve, reject) => setTimeout(resolve, 50, '氷上すみれ'))
const task3 = new Promise((resolve, reject) => setTimeout(resolve, 10, '新条ひなき'))
const taskError = new Promise((resolve, reject) => setTimeout(reject, 30, 'ジョニー別府'))

Promise.race([
  task1,
  task2,
  taskError
])
.then((res) => console.log(res))
.catch((error) => console.log(`Error: ${error}`))
// => Error: ジョニー別府

Promise.race([
  task1,
  task2,
  taskError
  task3,
])
.then((res) => console.log(res))
.catch((error) => console.log(`Error: ${error}`))
// => 新条ひなき

Promise.resolve() と Promise.reject()

非同期処理がない場合など、わざわざPromiseオブジェクトを返す必要がない関数の場合、関数内でPromise.resolve()Promise.reject()を返すとと呼び出し元のPromiseに返すことができます。
returnしないと、関数が終了したundefinedが返される

function hi(name) {
  if( name.indexOf('ジョニー') === -1 ) {
    return Promise.resolve(`Hi, ${name}!`)
  } else {
    return Promise.reject(`ERROR: You are ${name}!`)
  }
}

Promise.all([
  hi('紅林珠璃'),
  hi('天羽まどか'),
  hi('黒沢凛')
])
.then((res) => console.log(res))
.catch((error) => console.log(error))
//=> [ 'Hi, 紅林珠璃!', 'Hi, 天羽まどか!', 'Hi, 黒沢凛!' ]

Promise.all([
  hi('ヤミ'),
  hi('ミヤ'),
  hi('ジョニー別府'),
])
.then((res) => console.log(res))
.catch((error) => console.log(error))
//=> ERROR: You are ジョニー別府!

 
Promiseの基本的な使い方をまとめてみました。
実際の使われた方とかアンチパターンとかは、まだ解ってないですが... (その辺りはawait/asyncに移行しちゃってから考えたほうが良い気がします)


[参考]

JavaScript (ES2015) アロー関数 省略した書き方とかのメモ

ES2015/ES6で追加されたアロー関数()=>{...}thisを拘束できるのがメインだと思っているのですが、色々と省略した書き方ができるようで、まだ不慣れなのでライブラリとか参考のコード見てる時に何だっけ?となりがちだったので省略した書き方のメモ。

アロー関数の基本形

(param1, param2) => {
 // ... 処理
}

関数名を付けて定義

const func = (param) => {
  // ... 処理
}

引数が1つのとき()を省略できる

(param) => { ... }

↓ 省略形

param => { ... }

値を返すだけの関数のときreturn{}を省略できる

(param) => { return val }

returnを省略

(param) => { val }

{}も省略

(param) => val
{}だけ省略はエラーになる
(param) => return val
// SyntaxError: Unexpected token return

オブジェクトを返す時は()で囲う

返すオブジェクトを()で囲わないと、オブジェクトの{}が関数のブロックと解釈される
ex: {cute: '星宮いちご'}というオブジェクトを返す関数

const foo = (param) => {cute: '星宮いちご'}
foo() // undefined

()で囲ってないとエラーにはならないがundefinedが返る
返すオブジェクトを()で囲む ↓

const foo = (param) => ({cute: '星宮いちご'})
foo() // {cute: '星宮いちご'}

↓ 下記と同様

const foo = (param) => {
  return {cute: '星宮いちご'}
}

正直{ return {...} }で良くない?って感じる…

const foo = (param) => { return {cute: '星宮いちご'} }

慣れの問題かもだけど、引数の()を省略は見づらい... 値を返すだけの関数を1行で書いて{}return省略するのは、返す値がオブジェクトや複雑な計算式だとやっぱ見通しが悪い気がしてます。
チームでの開発ならprettierとかフォーマッターを導入するか、書き方のルールを決めておかないと、省略形が入り乱れると結構混乱しそうって思いました。


これから学ぶ JavaScript

これから学ぶ JavaScript

JavaScript (ES2015) オブジェクトのループについてのメモ

ES2016/ES6でfor...ofが追加されたので改めてオブジェクトのループの方法を見直そうと思いました。

for..of

for...of 文は、iterableオブジェクトに対して反復的な処理をするループを作成します(iterableオブジェクトには組み込みのString, Array, 例えば Array に似たargumentsNodeListオブジェクト、TypedArray, Map, Set, ユーザー定義のiterableなどが含まれます)。

for..of と for...in の違い

  • for...of は値を列挙する
  • for...in はプロパティ(インデックス)を "prototypeで拡張されたプロパティを含めて" 列挙する

ex

Object.prototype.objCustomFunc = function() {};
Array.prototype.arrCustomFunc = function() {};
let arr = ['星宮いちご', '霧矢あおい', '紫吹蘭'];
arr.unitName = 'ソレイユ';

// for...of
for (let val of arr) {
  console.log(val);
}
// => "星宮いちご", "霧矢あおい", "紫吹蘭"

// for...in
for (let i in arr) {
  console.log(i);
}
// => 0, 1, 2, "unitName", "arrCustomFunc", "objCustomFunc"

// hasOwnProperty を使えば自身のプロパティだけに限定できる
for (let i in arr) {
  if ( iterable.hasOwnProperty(i) ) {
    console.log(i);
  }
}
// => 0, 1, 2, "unitName"

for...of でオブジェクトをそのままループさせることはできない

ObjectはiterableオブジェクトではないのでTypeErrorになる

let obj = {
  cute:   '星宮いちご',
  cool:   '霧矢あおい',
  sexy:   '紫吹蘭',
  pop:    '夏樹みくる',
  legend: '神前美月'
};
for (let val of obj) {
  console.log(val);
}
// => TypeError: obj is not iterable

Object.keys()でキーの配列をfor...ofでループさせる

let obj = {
  cute:   '星宮いちご',
  cool:   '霧矢あおい',
  sexy:   '紫吹蘭',
  pop:    '夏樹みくる',
  legend: '神前美月'
};
Object.prototype.objCustomFunc = function() {};

// for...of Object.keys(obj)
for (let key of Object.keys(obj)) {
  console.log(key, obj[key]);
}
// cute 星宮いちご
// cool 霧矢あおい
// sexy 紫吹蘭
// pop 夏樹みくる
// legend 神前美月

Array.forEachを使う方法

Object.keys(obj).forEach(function(key) {
  console.log(key, this[key]);
}, obj);
// cute 星宮いちご
// cool 霧矢あおい
// sexy 紫吹蘭
// pop 夏樹みくる
// legend 神前美月

Array.forEachで実行する関数をアロー関数()=>にすると、関数内のthisが第二引数で渡したオブジェクトにならないので、第二引数を使用する場合は注意が必要

for...in obj を使う方法

for...inならオブジェクトのままループさせることができるが、配列と同様でprototypeで拡張されたプロパティも列挙される

for (let key in obj) {
  console.log(key, obj[key]);
}
// cute 星宮いちご
// cool 霧矢あおい
// sexy 紫吹蘭
// pop 夏樹みくる
// legend 神前美月
// objCustomFunc [Function]
所謂 for 文でループさせる
let keys = Object.keys(obj);
for (let i = 0, l = keys.length; i < l; i += 1) {
  console.log(keys[i], obj[ keys[i] ]);
}
// cute 星宮いちご
// cool 霧矢あおい
// sexy 紫吹蘭
// pop 夏樹みくる
// legend 神前美月

まとめ

オブジェクトをループさせたい時

  1. for...of Object.keys(obj) でループさせる
  2. Object.keys(obj).forEach でループさせる ※ ループ中にbreakはできない
  3. for...in obj でループさせ適時 obj.hasOwnProperty(key)で自身のプロパティかチェックする
  4. for文でオブジェクトのキーをインデックス順に列挙してループさせる

ループ中にbreakもできるのでES2015/ES6が使える環境なら、for...ofでループさせるのが一番ラクかなと思いました。
IEfor...of未対応なようなので、IE11必須みたいなWEBサイト制作ではbabelとか使わないとダメですね... IE... ξ(Ծ‸Ծ)ξ


[参考]

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発