コンテンツへスキップ

Seleniumをサポートするツールの紹介

Seleniumを使ってテストを行う際に役立つツール群を紹介します。これらを使うことでテストコードを書く手間が大幅に軽減されるはずです。

Selenium Builder

Selenium Builder

Selenium IDEと並ぶレコーディングツールです。Firefoxのプラグインとして動作しますが、最新のバージョンでは再生にSelenium Serverを利用するため、IEやChromeでも再生可能になりました。

次に、簡単に操作方法をご紹介します。

操作方法

対象のページを開き、FireFoxのオプションから、開発ツール「Launch Selenium Builder」を選択します。

Selenium Builder 01

記録ボタンをクリックして記録が開始されます。なお、左下より言語を選択できますので、好きな言語を選べます。

Selenium Builder 02

記録しているウィンドウは、タブのバックグラウンドカラーがグリーンに変わっています。

Selenium Builder 03

記録画面ではリアルタイムで、動作が表示されます。記録を終了する場合は、「記録を停止」ボタンをクリックします。

Selenium Builder 04

再生には、Seleniumサーバが必要になりますので、あらかじめSelenium Standalone Serverをダウンロードして、次のコマンドを実行します。

$ java -jar selenium-server-standalone-2.xx.x.jar

メニューの「実行」から「Selenium サーバで実行」を選択します。

Selenium Builder 05

Selenium サーバ設定画面が起動しますので、ホスト:ポートにlocalhost:4444を指定します。また、この例ではブラウザ設定にchromeを指定しています。

Selenium Builder 06

その後、「実行」をクリックするとブラウザが立ち上がり、テストケースが実行されます。Seleniumサーバを起動しなければならないので一手間が多くなっていますが、その分、各ブラウザでのテストが可能となっています。

次のSelenium IDEと比べても遜色ないので好みが分かれるところですが、お互い一長一短があります。テスト管理視点から見ても、両方に対応できるようにしておいた方がよいでしょう。

Selenium IDE

Selenium IDE

Seleniumの初期から存在するレコーディングツールです。こちらもFirefoxのプラグインとして動作します。

操作方法

起動は、メニューにボタンが追加されるので、クリックするだけです。

Selenium IDE

下図で簡単に操作方法を説明します。

  • 記録開始ボタンをクリックすると、レコ-ディングが始まり、もう一度クリックすると終了します。
  • 実行ボタンで、テストケースが実行されます。
  • 実行速度のスライダーでは、実行時に各コマンドの実行タイミングを遅らせたり、進めることができます。

Selenium IDE

操作方法の慣れの問題もあるので一概には言えませんが、Selenium IDEの方が、先出のSeleniumBuilderよりもコマンドの操作がやりやすく感じます。どちらも、非常に優れたツールですので、各ツールの特長を生かしてテストケース作成を行ってみて下さい。

Selenium Generator

Selenium Generator

Webサービスとして公開されている、サポートツールです。
Slenium IDEで作成されたテストケースを元に、データのバリエーション毎のテストケースを生成してくれます。

操作自体は非常に簡単で、テストケースをコピーして、バリエーション対象のデータを設定し、生成されたデータをコピーして、Selenium IDEに貼り付けるだけです。

データを入れ替えるだけのテストであれば、こちらを利用すると、かなり効率よく生成できるでしょう。

Selenium Page Object Generator

page object generator

Page Object modelを生成するChromeの拡張機能です。
表示されたページに対して、各要素に対してオブジェクトを生成し、その操作を行うメソッドなどが生成されます。

出力されたコードを利用して、Seleniumから各オブジェクトをコントロールすることが可能です。出力可能なコードは、Java、C#、Robot Frameworkとなっています。

最後に

Seleniumは、テストツールとして非常に簡易に利用できるのですが、だんだんとコードが増えるにつれ、テストパターンも増えていきます。プロジェクトが進んだり、開発保守が進むと、テストのために、テストコードをメンテナンスするという悪循環も生まれてきます。

その様な時にプラグインなどを利用することで、テストを「楽に」「深く」行うことができるようになります。Seleniumについては、他にも便利なプラグインなどがありますので、また次回以降紹介していきます。

CSSの適用順位について

スタイルシートは柔軟に設定ができる分、思わぬところで表示が崩れたり、どこの条件がマッチして適用されたのか分からなくなることがあります。そこでスタイル設定の適用順番について確認してみます。

スタイルシートの書き方について

スタイルを設置する際には幾つか書き方があります。ざっと上げただけでも以下があります。

  1. linkタグを使った場合
  2. @importを使った場合
  3. styleタグを使った場合
  4. style要素を使った場合

そしてこれらの順番に加えて、特に厄介なのが !importantを使った場合 ではないでしょうか。これによって優先順位が大幅に変わることが多々あります。

linkタグを使った場合

スタイルシートの設定は基本的に上書きです。

<link rel="stylesheet" href="css1.css">
<link rel="stylesheet" href="css2.css">

のように設定があった場合、重複する設定についてはcss2.cssで上書きされます。

css1.css
h1 {
color: black;
}

css2.css
h1 {
color: red;
}

この場合、h1は文字色が赤になります。

しかし!important指定が入った場合は、css1.cssのものが優先されます。

css1.css
h1 {
color: black !important;
}

なお、css2.cssの方にも!importantを指定した場合は赤くなります。つまり !important についても後から設定したものが優先されます。

css2.css
h1 {
color: red !important;
}

@import を使ってスタイルシートファイル内で外部ファイルを読み込む場合、宣言はファイルの一番上に書かなければならないというルールがあります。つまり @import より後に書いた設定が優先されるということです。

css2.css
@import url("css1.css");
h1 {
color: red !important;
}

styleタグを使った場合

styleタグを使ってHTMLファイル内にインラインでスタイル設定を記述できます。この場合も優先順位は後から指定したものになります。

<link rel="stylesheet" href="css1.css">
<style>
  h1 {
    color: green;
  }
</style>

このように指定すればh1は緑になります。こちらも!important指定が有効で、外部ファイルでも利用できます。

style要素を使った場合

スタイル要素はタグの中に直接指定する方式です。以下のように指定することで文字は黒になります。

<h1 style="color:black;">Hello World!</h1>

ただしこの指定も!important指定には負けますので注意してください。jQueryなどから指定しても!important指定があるとうまく反映されないことがあるでしょう。

スタイルシートのクラス/ID/タグ名の優先順位について

要素指定している場合は関係ありませんが、スタイルシートファイルやstyleタグで指定する方法としては3つあるかと思います。

h1 {
  color: green;
}
.h1 {
  color: yellow;
}
#h1 {
  color: grey !important;
}

<h1 id="h1" class="h1">Hello World!</h1>

このように指定した場合、最も強いのはID指定になります。順番を入れ替えたとしても #h1 の指定が優先されます。次に有線されるのはクラス指定(.h1)になります。最後に h1 というタグでの指定になります。

もちろん ID で指定しないスタイル設定であればクラスやタグで指定しているものが適用されます。


スタイルシートの設定はマジック的なところがあり、書く順番を間違えると表面上はエラーはなくとも思った表示と異なる結果になることがあります。適用される順番をきちんと覚えておく必要があるでしょう。

PitaliumのテストをSelenium IDEで作成する

PitaliumはSelenium WebDriverに機能追加していますので、Selenium IDEを使ってテストケースを作成できます。

要点としては、エクスポートの形式として Java / JUnit 4 / WebDriver を選択すると言うことです。

出力すると、次のようなコードが生成されます。 この中で必要なのは @Test 以下になります。

package com.example.tests;
 
import java.util.regex.Pattern;
import java.util.concurrent.TimeUnit;
import org.junit.*;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.openqa.selenium.*;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.Select;
 
public class Test {
  private WebDriver driver;
  private String baseUrl;
  private boolean acceptNextAlert = true;
  private StringBuffer verificationErrors = new StringBuffer();
 
  @Before
  public void setUp() throws Exception {
    driver = new FirefoxDriver();
    driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
  }
 
  @Test
  public void test() throws Exception {
    driver.get(baseUrl + “/ja/tutorial/todo/”);
    driver.findElement(By.id(“txtTodo”)).clear();
    driver.findElement(By.id(“txtTodo”)).sendKeys(“タスク”);
    driver.findElement(By.id(“btnRegist”)).click();
    driver.findElement(By.xpath(“(//input[@type=’checkbox’])[4]”)).click();
    driver.findElement(By.cssSelector(“#sizzle1453688248661 > td.content > span”)).click();
    driver.findElement(By.name(“content”)).clear();
    driver.findElement(By.name(“content”)).sendKeys(“本を返却します”);
    driver.findElement(By.id(“btnUpdate”)).click();
  }
 
  @After
  public void tearDown() throws Exception {
    driver.quit();
    String verificationErrorString = verificationErrors.toString();
    if (!””.equals(verificationErrorString)) {
      fail(verificationErrorString);
    }
  }
 
  private boolean isElementPresent(By by) {
    try {
      driver.findElement(by);
      return true;
    } catch (NoSuchElementException e) {
      return false;
    }
  }
 
  private boolean isAlertPresent() {
    try {
      driver.switchTo().alert();
      return true;
    } catch (NoAlertPresentException e) {
      return false;
    }
  }
 
  private String closeAlertAndGetItsText() {
    try {
      Alert alert = driver.switchTo().alert();
      String alertText = alert.getText();
      if (acceptNextAlert) {
        alert.accept();
      } else {
        alert.dismiss();
      }
      return alertText;
    } finally {
      acceptNextAlert = true;
    }
  }
}

抜き出すと次のようになります。

driver.get(baseUrl + “/ja/tutorial/todo/”);
driver.findElement(By.id(“txtTodo”)).clear();
driver.findElement(By.id(“txtTodo”)).sendKeys(“タスク”);
driver.findElement(By.id(“btnRegist”)).click();
driver.findElement(By.xpath(“(//input[@type=’checkbox’])[4]”)).click();
driver.findElement(By.cssSelector(“#sizzle1453688248661 > td.content > span”)).click();
driver.findElement(By.name(“content”)).clear();
driver.findElement(By.name(“content”)).sendKeys(“本を返却します”);
driver.findElement(By.id(“btnUpdate”)).click();

この中でも sizzle1453688248661 といった指定は他で動かない可能性が高い(実際に動かなかった)ので除外します。後は自由に assertionView.assertView("PageName"); を入れていくだけでテストスクリプトができあがります。

注意点としては、Selenium IDEで作ったテストは一瞬で流れてしまうので適度にwaitを入れる必要があります。Pitaliumでページ読み込み完了まで待つには? | hifive開発者ブログを参考に、

PtlWebDriverWait wait = new PtlWebDriverWait(driver, 30);
wait.untilLoad();

を差し込んでください。

Bootstrapカスタマイズサービスまとめ

Bootstrapは簡単にwebサイトが作れるフレームワークとしてここ数年来人気があります。レスポンシブデザインの台頭でWeb制作の現場でも、より早い段階で実際に動かせるプロトタイプが必要となる場面が増えました。Bootstrapで少しカスタマイズして使いたいという方やガッツリカスタマイズしたい人さまざまだと思います。ここではBootstrapのカスタマイズを簡単にできるサービスを集めてみました。

1.Bootstrap Live Customizer

右側の画面でカスタマイズして、完成したら左側のGetボタンからダウンロードします。色の変更にはカラーバーのようなツールを使用するか、直接カラーコードをいれるかいずれの方法にも対応しています、お好きな方でつかってみるといいでしょう。(有料: $14 2016.2時点)

2.Lavish Bootstrap

画像からカラーパターンを取り出し、bootstrapのテーマに取り出したカラーを当てはめ、カスタマイズすることができます。主な用途はメインに使用する画像から合うカラーを取り出し、bootstrapをカスタマイズすることですが、メイン画像以外にもロゴマークなどからサイトの配色を決めるのにも使えそうです。

3.Bootswatchr

bootstrapの変数を変更し、見た目を変更することができる。画面右側にソースコードが出てくるので、ソースの変数のカラーコードを変更すると、右側のプレビュー画面表示されます。

4.BootStrap Magic

ブラウザ上でbootstrapのテーマを作ることができるサービスで、Bootstrap2.3.2からBootstrap3.1に対応。画面右側の@gray-darkerなどの変数が書かれている箇所に16進数カラーコードをいれて、各部位の色を変更することができます。Bootswatchrに比べると、ソースではなくテキストボックスに入れる形なので、CSSやLESSの開発に慣れていない人でもとっつきやすい点が挙げられます。

5.StyleBootstrap.info: Twitter Bootstrap theme generator

Bootstrapの画面のパーツごとにカラーリング、文字の大きさ、隙間などを細かく調整できるサービスです。カラーテーマを作るのであればこれをベースにすると良さそうです。

6.Customize and download · Jasny Bootstrap

ビジュアル的ではなく、LESSでの変数やjQueryで使う機能などを設定した上でカスタマイズされたテーマをダウンロードできます。よりプログラマブルなカスタマイズが可能となっています。

まとめ

いかがでしたでしょうか。ますます登場の機会が増えてきているBootstrap。使用する際にカスタマイズが必須となってきています。開発工数短縮のためにも、素早くプロトタイプを作れることが求められてきている今日このごろ、今回紹介したようなカスタマイズサービスが重宝することでしょう。

オンラインのJavaScript学習サイトまとめ

JavaScriptはWebブラウザさえあればはじめられるという手軽さの反面、学習しようと思った際のステップはあまり用意されていないように見えます。しかしJavaScriptの入門になる学習サイトも増えてきています。

そうしたオンラインサイトを使ってJavaScriptの学習をはじめてみるのはいかがでしょう。

ドットインストール

3分動画を通じてプログラミングを学べるドットインストールのJavaScriptタグでは多くのJavaScriptを使ったレッスンが登録されています。入門から実際にWebアプリケーションを作ってみる実践的なレッスンなどレベルも幅広く用意されています。

CodeStudy

CodeStudyではWebブラウザ上でゲーム感覚でJavaScriptが学べるようになっています。シェアする機能を通じて友達と学習レベルを競ったり、分からないところを聞いたりすることもできます。

ShareWis

動画を通じてJavaScriptの基礎を学べます。変数、プロンプト、配列といった基礎的な部分の学習が可能です。

CODEPREP

JavaScriptの基礎、演算、変数、条件分岐、配列、関数、DOMといった具合にステップアップしながらJavaScriptの学習ができます。

code.9leap.net

JavaScriptとHTML5をゲームの開発を通じて学習します。ライブラリとしてenchant.jsを使いますのでJavaScriptの基礎は分かっていて、ゲーム開発を学びたいという方向けでしょう。

Khan Academy

数学や化学などの学習サイトとして知られるカーンアカデミーでもコンピュータプログラミングとしてJavaScriptを学べます。動画を見ながら学習していく形式です。

Code School

JavaScript自体、jQuery、Angular.jsやEmber、Backbone.jsといったクライアントフレームワークやnode.jsまで含めてJavaScriptを学べるサイトになっています。

TheCodePlayer

JavaScriptの基礎と言うよりも特定のTipsをJavaScript/スタイルシート/HTMLを使って実現する方法を学べます。アニメーションなど見た目のエフェクトが多いようです。

Code Avengers

実際にオンライン上でコードを書きながらJavaScriptによるゲーム開発を学べます。JavaScriptの基礎は分かっている方向けです。

マンガで分かる JavaScriptプログラミング講座

マンガを通じてJavaScriptのプログラミング方法を学習できます。コンテンツは若干古いですがJavaScriptの基礎は変わりませんので今も役立つでしょう。

JavaScriptワークブック

画像でダウンロードできるJavaScriptワークブックです。印刷して学習する形なので、会社の講習など大人数で学習する際には使いやすいかも知れません。

W3C School

W3Cが提供しているオンライン学習システムです。JavaScriptの文法などに沿って多数のメニューが用意されています。チュートリアル兼学習といった形です。

Codecademy

Codeacademyが提供しているレッスンです。学習時間は約10時間で、初級者向けとのことです。最終的にお店のレジのようなWebアプリケーションを作ります。


JavaScriptはスクリプト言語ということもあって、適切なルールのもとに書くようにしないとあっという間にコードがこんがらがって破綻する可能性があります。それを防ぐためにもまず基礎をきちんと押さえる必要があるでしょう。

さらに大型化する中では何らかのフレームワークを用いて書き方、構築法を統一すべきです。hifiveもその際の選択肢にぜひ!

hifive ソースコードリーディングを開催します

先日、hifiveの最新バージョンである1.2をリリースしました。入力チェックであったり、SPAをサポートするシーンといった機能が追加されています。

フレームワークというのはいくらオープンソースと言っても、機能がブラックボックス化されていることが多いかと思います。jQueryの動作原理についてきちんと把握している人は多くないのではないでしょうか。しかしフレームワークを信頼し、使い込んでいく上でも全体のコードを把握するのは必要なことです。

そこで今回はhifive 1.2をベースにソースコードリーディングを開催します。hifiveではブラウザ互換性を保つための工夫が様々にこらされていたり、MVCを維持してメンテナンス性を高めるための仕組みが盛り込まれています。hifiveを使わなかったとしてもHTML5を利用する上でのハマりどころであったり、レガシーブラウザでの予想もしなかったバグにハマるような事態が防げるようになるはずです。

ソースコードリーディングにつき、少人数での開催となります。ご興味があればぜひご参加ください!

hifive ソースコードリーディング – connpass

マルチプラットフォームに対応したプログラミングエディタまとめ

今回はJavaScriptなどWebフロントエンド開発向きのプログラミングエディタを紹介します。今回はマルチプラットフォーム限定になります。マルチプラットフォームに対応していればWindows/Mac OSX/Linuxなど多くの環境で共通の開発環境として利用できるでしょう。

Atom

GitHub社が開発しているプログラミングエディタです。Electron(旧atom-shell)を使って開発されています。HTML5/JavaScriptを使って作られています。多くのテーマ、機能拡張がコミュニティベースで作られています。

Sublime Text: The text editor you’ll fall in love with

有償のエディタです(70ドル)。プラグインが数多く作られています。複数の箇所を選択して、一括修正する機能があります。キーバインドはもちろん、メニューやスニペット、マクロなどカスタマイズできる範囲がとても広いです。

GNU Emacs – GNU Project – Free Software Foundation (FSF)

昔からあるエディタです。一般的なプログラミングエディタに比べると起動が重たいので、一度立ち上げると滅多に落としません。Common Lispを使って拡張を作ることができます。CUI/GUI、両方で動作します。

welcome home : vim online

Emacsと並んで昔からあるプログラミングエディタです。プラグイン、テーマともに数多く提供されています。viは多くのLinuxディストリビューションにおいてデフォルトでインストールされているので、Vim/viのキーバインドに慣れているとサーバでのエディタ操作が便利になります。

Visual Studio Code – Code Editing. Redefined

Microsoft社が開発しているプログラミングエディタです。Atomをベースにしています。プラグインの仕組みは異なるので、Atomほどの数は揃っていません。しかしデフォルトでGit連携機能やデバッグ機能が組み込まれています。

Light Table

元々Kickstarterで出資を仰いで開発されたエディタです。エディタとその結果のプレビューが並んでおり、さらにプレビューでの変数の値をエディタ側で確認することができます。プラグインも数多く開発されています。

Brackets – A modern, open source code editor that understands web design.

Adobe社が開発しているプログラミングエディタです。主にHTML5アプリケーションの開発に向いています。PSDファイルのレイヤー情報を読み取るなど、Adobe社製品との親和性が高くなっています。

Komodo Edit | Komodo IDE

同社の開発するKomodo IDE(有償)のエディタ機能を取り出したエディタです。こちらはオープンソースです。エディタ機能はもちろん、Markdownプレビューやフォルダのプロジェクト管理機能があります。


個人的にはEmacsを使い続けていて、最近Atomに乗り換えました。エディタも進化しているので、モダンな開発プロジェクトにおいては最新のエディタの方が対応が早いことが多いようです。

各プラットフォーム限定のエディタも多いですが、複数プラットフォームを使うことが多い方はぜひマルチプラットフォーム対応のものを選んでください。

Promise処理の中での例外エラーの扱いについて

JavaScriptで開発するにあたってPromise処理は欠かせません。非同期処理が頻繁に使われるため、前の処理が適切に終了したのか、または失敗したのかを通知する仕組みとしてPromiseが使われます。

通常、Promiseは次のような記述となります(hifiveでの記述です)。testという関数が非同期処理である場合、その結果を受け取ってthenまたはfailに送信されます。

var deffered = h5.async.deferred();
var test = function (count) {
  setTimeout(function () {
    try {
      console.log('count', count);
      if (count === 1) {
        deffered.resolve(count)
      } else {
        throw count;
      }
    }
    catch(err) {
      return deffered.reject(err)
    }
  }, 1000);
};
var promise = deffered.promise();
test(1);
promise.then(function(msg) {
  console.log("success", msg);
}).fail(function(msg) {
  console.log("fail", msg);
});

Promise内では処理が成功したらresolve、失敗したらrejectを呼び出します。そのため分かりやすい形としては処理全体をtry/catchで囲んでしまい、何か問題があればcatchに飛ばしてしまうことです。もちろんコード内でもrejectに飛ばす場合もあるでしょう。

上記処理を実行すると、

count – 2
success 1

というログが出ます。

thenの中でエラーが起こるとどうなるか

さて、例えば then 処理の中で例外処理が起こった場合はどうなるでしょうか。コードとしては次のようになります。

var deffered = h5.async.deferred();
var test = function (count) {
  setTimeout(function () {
    try {
      console.log('count', count);
      if (count === 1) {
        deffered.resolve(count)
      } else {
        throw count;
      }
    }
    catch(err) {
      return deffered.reject(err)
    }
  }, 1000);
};
var promise = deffered.promise();
test(1);
promise.then(function(msg) {
  console.log("success", msg);
  throw 2; // 追加
}).fail(function(msg) {
  console.log("fail", msg);
});

期待するところとしては、.fail が呼び出されることかも知れませんが、実際にはそうはなりません。すでにPromiseとしては処理が完了しているためです。

ただし、流れとしては try/catchに囲まれた場所であるために catch が呼び出されます。ただしこの後に deffered.reject(err) を呼び出しても何も起こらないようです。

ES6ではどうなるのか

ECMAScript6では標準でPromiseが搭載されます。その場合は次のような記述になります。

var promise = new Promise(function(resolve, reject) {
  setTimeout(function () {
    var count = 1;
    try {
      console.log('count', count);
      if (count === 1) {
        resolve(count)
      } else {
        throw count;
      }
    }
    catch(err) {
      console.log("Error!")
      return reject(err);
    }
  }, 1000);
});

promise.then(function(msg) {
  console.log("success", msg);
  throw 2;
  console.log("Exception called");
}, function(msg) {
  console.log("fail", msg);
});

ECMAScript6の場合、Promiseはthenのみがあり、一つ目がresolve、二つ目がreject相当になります。上記のようなコードの場合、

count 1
success 1
Uncaught (in promise) 2

といったログが表示されます。二つ目の関数に飛ぶわけではありません。


Promise自体、ようやく標準化が定まった状態で、jQuery 3系でも修正が予定されています。今後はECMAScript6に沿った形になっていくことでしょう。then処理内での例外発生は予定していなかった場所がコールされる可能性があるので注意が必要そうです。

Promiseを使った非同期ループ処理の書き方について

JavaScriptで常に頭を悩ませるのが非同期処理ではないかと思います。非同期処理を幾つも実行したりすると、思ったタイミングで処理が走らないといったことが多々あります。

そんな中でループ処理になると、特に厄介ではないでしょうか。そこで今回はPromiseを使ったループ処理について紹介します。

0から10まで順番に処理をしたら抜けるループ

非同期処理でない場合は次のように書けます。


for (var i = 0; i <= 10; i++) {
  console.log(i)
}
console.log("Finish");

view raw

index.js

hosted with ❤ by GitHub

厄介なのは非同期処理時です。まず、基本形として次のようにPromiseを考えます。


new Promise(function(res, rej) {
 
}).then(function() {
  console.log("Finish");
})

view raw

index.js

hosted with ❤ by GitHub

この処理は一瞬で終了してしまいます。コンソールにも特に何もメッセージは出ません。この中でループ処理を行うようにします。この時、注意するのは非同期処理がターゲットと言うことです。そのため、loop関数の中でもPromiseを使って処理を行うようにして、処理順番を保証します。


new Promise(function(res, rej) {
  function loop(i) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log(i);
            resolve(i+1);
        }, 100);
    })
  }
  loop(0);
}).then(function() {
  console.log("Finish");
})

view raw

index.js

hosted with ❤ by GitHub

これにより、非同期処理(setTimeout)が実行された後、resolveが実行されるようになります。後は内側のPromiseについて、then処理を書きます。


// ループ処理の完了を受け取るPromise
new Promise(function(res, rej) {
  // ループ処理(再帰的に呼び出し)
  function loop(i) {
    // 非同期処理なのでPromiseを利用
    return new Promise(function(resolve, reject) {
      // 非同期処理部分
      setTimeout(function() {
        console.log(i);
        // resolveを呼び出し
        resolve(i+1);
      }, 100);
    })
    .then(function(count) {
      // ループを抜けるかどうかの判定
      if (count > 10) {
        // 抜ける(外側のPromiseのresolve判定を実行)
        res();
      } else {
        // 再帰的に実行
        loop(count);
      }
    });
  }
  // 初回実行
  loop(0);
}).then(function() {
  // ループ処理が終わったらここにくる
  console.log("Finish");
})

view raw

index.js

hosted with ❤ by GitHub

thenの中で処理判定を行い、ループを抜けるかどうかの判定を行っています。これで非同期処理における処理順番の保証と、処理完了時に次の処理につながる部分ができました。


JavaScriptにおける非同期処理のループはかなり面倒であるというのが分かるかと思います。JSDoc: Namespace: asyncを使うと非同期処理におけるループがこれくらい分かりやすく書けるようになります。以下は処理を5回繰り返すという指定です。

 


var loop = h5.async.loop(new Array(5), function (i, v) {
  var deffered = h5.async.deferred();
  var loop = function (count) {
    setTimeout(function () {
      console.log(count);
      deffered.resolve(count);
    }, 100);
  };
  loop(i);
  return deffered.promise();
}).done(function () {
  console.log("処理完了");
});

view raw

index.js

hosted with ❤ by GitHub

hifiveのPromise(Deferred)を使って非同期処理を順番に処理しよう

JavaScriptは非同期処理が得意な言語です。たとえば以下のコードは1秒後にHello Worldを出力します。

setTimeout(function () {
  console.log("Hello World");
}, 1000)

また、Ajaxも非同期処理になるので、かつてはコールバック関数を使って処理を書いていました。

$.ajax({
  /* 処理 */
  success: function () {
    // ネットワーク処理成功時
  },
  error: function () {
    // ネットワーク処理失敗時
  }
});

しかしこの書き方では2回目のネットワーク処理、3回目のネットワーク処理…と続けていこうとした際にどんどんネストが深くなってしまう問題がありました。そこで使われるようになっているのがPromiseになります。

Promiseでは非同期処理をまとめて行えるようになっており、各非同期処理がPromiseオブジェクトを返却してつなげていくことができます。

var func1 = function () {
  // 非同期処理
}.then(function () {
  // 次の非同期処理
});

そして複数の非同期処理をまとめる際にはwhenを使います。

Promise.when(function () {
  /* 非同期処理 */
}, function () {
  /* 非同期処理 */
}).then(function () {
  // 処理完了
});

ただしこの場合、各非同期処理の実行順番については保証されません。例えば次のようなコードがあったとします。

for (var i = 0; i <= 5; i++) {
  var test = function (count) {
    var randnum = Math.floor( Math.random() * 100 );
    setTimeout(function () {
      console.log(count);
    }, randnum * 100);
  };
  test(i);
}

これを実行すると、

5
3
1
0
2
4

のように表示されたりします(実行時によって結果が変わります)。これを正しく0〜5といった感じに並ぶようにするにはどうしたら良いでしょうか。

Promise.allを試してみる

一部のブラウザではPromiseが使えるようになっています。これのPromise.allを使ってみます。

ary = [];
for (var i = 0; i <= 5; i++) {
  var promise = new Promise(function(resolve, reject){
    var test = function (count) {
      var randnum = Math.floor( Math.random() * 100 );
      setTimeout(function () {
        console.log(count);
        resolve(count);
      }, randnum * 100);
    };
    test(i);
  });
  ary.push(promise);
}

Promise.all(ary).then(function (results) {
  console.log(results);
});

この方法の場合、Promise.allの結果としては [0, 1, 2, 3, 4, 5] といった形で正しく入ってきますが、その前のログは

1
3
5
4
2
0

といった形で実行順番は保証されていません。

hifiveのh5.async.loopを使う

そこで使ってみて欲しいのがh5.async.loopです。h5.async.loopを使うと実行順番を保証したPromiseでのループ処理が書けます。

var loop = h5.async.loop(new Array(5), function (i, v) {
  var deffered = h5.async.deferred();
  var randnum = Math.floor( Math.random() * 100 );
  var test = function (count) {
    setTimeout(function () {
      console.log(count);
      deffered.resolve(count);
    }, randnum * 100);
  };
  test(i);
  return deffered.promise();
});

loop.done(function () {
  console.log("処理完了");
});

のように書きます。h5.async.loopの最初の引数は配列になります。例えば [‘a’, ‘b’, ‘c’, ‘d’, ‘e’] のような配列を渡せば、iにはインデックス(0〜)が、vには値(aまたはbなど)が順番に入ります。

そしてまず最初にdeferredを生成します。そして最後にdeferred.promise()を返します。後はその中で非同期処理を実行し、deferred.resolve(成功時)、deferred.reject(失敗時)を呼び出すだけです。

そして h5.async.loop の done メソッドには各Promiseでのresolve時に渡した引数が入ってきます。これだけで実行順番も保証された非同期のループ処理が書けます。

メリット

Ajax処理においては同期、非同期の選択ができます。そのためいざとなれば同期処理にすることで実行順番の保証ができます。しかしsetTimeoutのような処理はできません。h5.async.loopを使えばどんな非同期処理においても使えますので便利です。

また、実行順番が保証されますので安心して処理が実行できます。ただし前の関数の実行結果を使いたいといったような処理を書こうと思うと面倒になりますので、その場合はPromiseオブジェクトをthenメソッドでつないでくのが良いでしょう。


hifiveのPromise(Deferred)を使えばコールバック地獄から解放され、見通しの良いコードが書けるようになります。さらに実行順番を保証したループ処理も簡単です。詳細は非同期処理とPromise(Deferred)を背景から理解しよう – hifiveをご覧ください。ぜひ使ってみてください!