かもメモ

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

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に移行しちゃってから考えたほうが良い気がします)


[参考]

Mac Apache2.4が動かなくなった

昔作ったサイトのメンテで久々にhomebrewで入れたローカル環境のApacheを起動しようとしたら次のようなエラーが出て起動できなくなっていました。

httpd: Syntax error on line 170 of /usr/local/etc/apache2/2.4/httpd.conf:
Cannot load /usr/local/opt/php56/libexec/apache2/libphp5.so into server: dlopen(/usr/local/opt/php56/libexec/apache2/libphp5.so, 10):
Library not loaded: /usr/local/lib/libjpeg.8.dylib\n
Referenced from: /usr/local/opt/php56/libexec/apache2/libphp5.so\n
Reason: image not found

libjpeg.8.dylibが無いのが原因っぽいです。
/user/local/Cellar/jpegを見に行くと9clibjpeg.9.dylibにバージョンアップされていました。(brew updateや何かのパッケージのアップデートの際に消えたのでしょうか...)

libjpeg を前のバージョンに切り替える

brew cleanupをしていなければ、古いバージョンのファイルが残っているので、次のコマンドで古いバージョンに切り替えられるようです。

$ brew switch libjpeg 8d

brew cleanupをすでにしてしまっていた場合

私は既にbrew cleanupしてしまっていたので、上記では切り替えることができませんでした。
php72 install libjpeg.8.dylib not found · Issue #4358 · Homebrew/homebrew-php · GitHub
こちらのコメントを参考に手動でlibjpeg.8.dylibをインストールしました。

$ wget -c http://www.ijg.org/files/jpegsrc.v8d.tar.gz
$ tar xzf jpegsrc.v8d.tar.gz
$ cd jpeg-8d
$ ./configure
$ make
$ cp ./.libs/libjpeg.8.dylib /usr/local/opt/jpeg/lib

このままでは、まだlibjpeg.8.dylibが読み込めないようだったので、/usr/local/lib/内にlibjpeg.8.dylibシンボリックリンクを作成しました。

$ cd /usr/local/lib/
$ ln -s ../opt/jpeg/lib/libjpeg.8.dylib libjpeg.8.dylib

これで無事Apacheが動く状態に戻せました。
ただ、/usr/local/opt/jpeg/lib/usr/local/Cellar/jpeg/9c/lib/シンボリックリンクなようでパスを見る感じだとインストールしたlibjpeg.8.dylib9cという別バージョンのフォルダに入ってしまっています。少し気持ち悪いですし、またlibjpegがアップデートされると問題が発生しそうなので、恒久的解決にはなって無さそうです...


[参考]

[未解決っぽい] Mac/Chrome カスペルスキーアップデートしたらTwitterの画像やビデオ・CSSも読み込まなくなった件

Macですが、いちおセキュリティソフトとしてカスペルスキーを入れていました。
カスペルスキーにアップデートで再起動しろと言われたので再起動したところ、Chromeで安息の地Twitterの画像やビデオ、だけでなくCSSまで読み込まなくなっていました。

読み込めてないエラーはこんな感じ

GET *.twimg.com <画像とかのURL>
net::ERR_SSL_VERSION_INTERFERENCE

どうやら別ドメインにあるコンテンツの取得が弾かれているようです。
カスペルスキーを終了すると問題なくTwitterが表示されたので、カスペルスキーのアップデートが原因っぽい…

  • Kaspersky Internet Security v 19.0.0.294a.b

心の安らぎTwitterを見れなくするとか許さん!
とはいえ、セキュリティ機能をOFFにしてしまうと本末転倒です。(ライセンス購入してるし...)

カスペルスキーにバグ報告をしたところ、「プロテクションのネット決済保護 ~ (HTTPS)」機能が原因で、現在調査中なので暫くその機能をOFFにしてくださいとの事でした。追記 2018 11/11

ウェブ保護・危険サイト診断が原因っぽい。

見れない状態のTwitterで開発者ツールをみると下記URL関係の読み込みがエラーになっていました。

  • CSS関連の //abs.twimg.com
  • 投稿された画像関連の //pbs.twimg.com
  • Gif動画などビデオ関連の //video.twimg.com

上記URLが読み込めるようになれば約束の地を取り戻せそうです。

1. カスペルスキーの脅威の除外リストにTwitter関係のURLを入れる

  • カスペルスキーのメニューから「環境設定 > 脅威」を選択
    f:id:kikiki-kiki:20181102071955p:plain -「除外するリストの編集...」を選択
  • 「信頼するWebサイトのアドレス」にTwitter関連のURLを入れる (とりあえず片っ端から入れたので無駄があるかも)
    f:id:kikiki-kiki:20181102082351p:plain

追加したリストは下記の通りです
twimg.comの読み込みが許可されれば、問題無さそうなのですが一応サブドメインも追加しておきました。

https://twitter.com
https://twimg.com
https://pbs.twimg.com
https://abs.twimg.com
https://video.twimg.com

この状態で、Twitterをリロードしても画像など表示されないままでした…

2. Chrome拡張が原因?

f:id:kikiki-kiki:20181102071926p:plain
Chrome拡張プラグインの「ウェブ保護」「危険サイト診断」をオフににすると、Twitterの画像を読み込めるようになりました。ただ、せっかくあるセキュリティ機能をOFFにしたままだと、意味がないのでこの機能をONにできる方法を探ってみました。

3. ウェブの保護の有効化をChromeの拡張から行うとダメだった

色々再起動やON/OFFを繰り返してみた結果、どうやら「ウェブの保護」をChrome拡張からONにするとダメなようでした。(アプリ側の除外設定が読み込まれない?)
「ウェブの保護」をONにする為に下記手順を行いました。

  1. 再度Chrome拡張の「ウェブ保護」・「危険サイト診断」をOFFにします
  2. Twitterをリロードして、画像などが表示されていることを確認
  3. Chromeカスペルスキーを念の為、再起動
  4. (Twitterをリロードして、画像などが表示されていることを確認
  5. Chrome拡張の「危険サイト診断」をONにする
  6. (Twitterをリロードして、画像などが表示されていることを確認
  7. カスペルスキーのアプリからウェブ保護を「有効にする」
    f:id:kikiki-kiki:20181102072036p:plain
    これでChrome拡張の「ウェブ保護」がONになります
  8. 最後にTwitterをリロードして、画像などが表示されていればOK

4. プロテクションのHTTPSの設定のせい?

上記で一旦 画像などが読み込めるようになったのですが、ページ遷移したりすると、また読み込めないものが出てくるという状態になりました。
読み込めてないエラーは以前と同じくnet::ERR_SSL_VERSION_INTERFERENCE
SSL/HTTPS関係っぽい...

カスペルスキー > 環境設定 > プロテクション設定 の全般から 「□ ネット決済保護 ~ (HTTPS)」のチェックを外すと上記のエラーは発生しなくなりました。
f:id:kikiki-kiki:20181102091103p:plain

その後、再び項目にチェックをしてTwitterをリロードしたら画像が読み込めていました... (なるほど、よくわからん!  

各手順ごとに念の為Twitterを再起動して問題がないか確認しています。
表示されなくなるステップがあれば、その箇所あたりに原因があるハズ。

リストにURLを追加した後は、下記手順で

  1. カスペルスキーのプロテクション設定から「HTTP」のチェックを外す
  2. Chrome拡張から「WEBの保護をOFF」
  3. カスペルスキー
  4. カスペルスキーを再起動
  5. カスペルスキーのプロテクション「WEBの保護を有効にする」
  6. カスペルスキーのプロテクション「HTTPS」に再チェック
    ※ 再チェックをして読み込めなくなったらチェックを外すと読み込めるっぽい。

 
脅威のスキャンの除外リストに入れているのでTwitterそのものに危険が入っちゃうと危険が危ないのですが、とりあえずコレで安息のインターネッツライフを取り戻せる状態になりました。色々試してみながら、たまたま上手く行っただけなので上記手順でうまくいかない場合はリストの見直しや、拡張のON/OFF・カスペルスキーの再起動など色々順番を変えて試してみるとうまくいくかもです。(確証ないですが...)

しかし、Chromeを再起動するとモーダルで画像は表示されるのに、タイムライン上だと画像が白枠になって表示されない状態になるものがあったり、ページ遷移で読み込めなくなってしまったりします。
とりあえず「プロテクションのHTTPS」のチェックを外すとリロードで読み込める場合が多いのですが、セキュリティ上はあまりよろしくない状態な気もします。

この問題はカスペルスキーがブロックしてる問題と、ブラウザChromeが読み込んだページをキャッシングしてしまうために、どれがクリティカルな問題になっているのかが大変わかりづらくなっているように思います...


安住の地 1 (BIC COMICS IKKI)

安住の地 1 (BIC COMICS IKKI)