Node.jsエンジニアなら2014年内に知っておきたいPromise入門

Promiseは非同期処理をベースにした並列処理の実装方法の一種です。Promiseでは並列処理の各タスクが必ず1回実行されることが保証され、タスクとタスクからの結果を取得する処理を分離することできます。また、タスクの並列処理/直列処理をユーザーが自由に制御することができます。

ECMAScript Language Specification 6th EditionにPromiseが追加されたことから、しばらくの間JavaScriptの非同期処理に関する話題の中心はPromiseになると思われます。

Node.jsでも既にPromise対応したモジュールも多く、Promiseでのみ非同期処理を提供しているモジュールも存在します。Promiseを使用する機会は今後必然的に増えていくでしょう。

今回のエントリーでは、Node.jsでPromiseを使用する方法を説明します。コード例を中心に理論的な細部にはあまり立ち入らないよう構成していますので、はじめてPromiseに触れる方も簡単に理解できる内容になっています。加えて、ニーズの高いと思われるasync.jsとPromiseの変換方法も紹介しています。

ぜひこの機会にPromiseをプログラミングスキルに追加してください。

1. Promiseモジュールのインストール

現在開発中のNode.js v0.12では--harmonyオプションを指定して実行することによりネイティブのPromise実装を使用することができます。しかし、v0.12はまだmasterブランチ上にしか存在せず、現時点の安定版であるV0.10系列のNode.jsではPromiseを実装したモジュールを別途インストールすることにより使用できるようになります。

npmパッケージとして使用可能なPromiseモジュールには以下のものがあります。

今回はこれらの中からbluebirdモジュールを使用することにします。

bluebirdはECMAScriptの仕様に準拠したインタフェースを持っており、Promise非対応メソッドをPromise対応させるpromisifyAllという大変便利な機能も実装されています。他のモジュールも基本的な使い方は変わりませんので、bluebirdでPromiseの使い方を覚えたら色々と試してみるのも良いかと思います。

1-1. bluebirdモジュールのインストール方法

npmコマンドを実行してモジュールをインストールします。

$ npm install bluebird
npm http GET https://registry.npmjs.org/bluebird
npm http 304 https://registry.npmjs.org/bluebird
[email protected] node_modules/bluebird

実行するとnode_modules/bluebirdディレクトリにモジュールがインストールされます。スクリプトを作成する際は、npmコマンドを実行したディレクトリ(node_modeulsディレクトリの親ディレクトリ)に置いてください。

2. First promise

bluebirdモジュールのインストールが終わったら、Promiseを使った最初のスクリプトを書いてみましょう。以下のスクリプトをfirst_promise.jsとして保存してください。

// first_promise.js
var fs = require('fs');
var Promise = require('bluebird');
Promise.promisifyAll(fs);

fs.readFileAsync('first_promise.js', 'utf-8').then(function(value) {
  console.log(value);
}, function(error) {
  console.error(error);
});

このスクリプトは自分自身(first_promise.js)を読み込んでそのまま出力します。スクリプトを実行すると以下の出力が得られます。

$ node ./first_promise.js 
// first_promise.js
var fs = require('fs');
var Promise = require('bluebird');
Promise.promisifyAll(fs);

fs.readFileAsync('first_promise.js', 'utf-8').then(function(value) {
  console.log(value);
}, function(error) {
  console.error(error);
});

$

first_promise.jsでは以下の処理を実行しています。

  1. fsモジュールとbluebirdモジュールをrequireで読み込みます。
  2. Promise.promisifyAllを実行し、fsモジュールのすべてのメソッドをPromise対応させます。
  3. fs.readFileAsyncを実行します。(readFileAsyncメソッドはPromise.promisifyAllがfsモジュールに追加した、Promise対応版のreadFileメソッドです。Promise.promisifyAll*Asyncという名前でPromise対応版メソッドをモジュールに追加します。)
  4. readFileAsyncが返すPromiseオブジェクトの.thenメソッドを使用し非同期処理終了後に実行されるコールバック処理を登録します。

このようにPromiseでは.thenメソッドに非同期処理が完了した後に実行すべき処理を記述します。

fs.readFileAsync関数はPromiseオブジェクトを返し、.thenはそのPromiseオブジェクトのメソッドです。

2-1. .then

.thenメソッドは、非同期処理に成功した場合に呼び出されるonFulfilledと、失敗した場合に呼び出されるonRejectedの2つの関数を引数を受け取ります。

 promise.then(onFulfilled, onRejected)

onFulfilledコールバック関数は非同期処理が成功した場合に、非同期処理から返された値を引数に実行されます。

 onFulfilled(value)

onRejectedコールバック関数は非同期処理が失敗した場合に、非同期処理から返されたエラーを引数に実行されます。

 onRejected(error)

onFulfilledonRejectedともにオプション引数で省略可能です。関数以外が渡された場合は無効にされます。

3. .thenメソッドチェイン

.thenメソッドはメソッドチェインとして使用されることを前提に設計されています。

以下のスクリプトsecond_promise.jsは、先ほどのfirst_promise.jsを少し改造して.thenメソッドをチェインさせたものです。

// second_promise.js
var fs = require('fs');
var Promise = require('bluebird');
Promise.promisifyAll(fs);

fs.readFileAsync('second_promise.js', 'utf-8').then(function(value) {
  console.log(value);

  return new Promise(function(onFulfilled, onRejected) {
    setTimeout(function() {
      onFulfilled('setTimeout completed');
      //onRejected(new Error('setTimeout failed'));
    }, 1000);
  });

}).then(function(value) {
  console.log(value);
}, function(error) {
  console.error(error);
});

second_promise.js では以下の処理を行っています。

  1. fs.readFileAsyncsecond_promise.jsを読み込み出力する部分まではfirst_promise.jsと同じです。
  2. fs.readFileAsync.thenメソッドのコールバックではnew Promiseで非同期処理を実行するPromise オブジェクトを返します。Promiseオブジェクトのコンストラクタに渡される関数はリゾルバ関数と呼ばれ、Promiseオブジェクトが返された次の.thenonFulfilledonRejectedを引数に実行されます。
  3. リゾルバ関数はsetTimeoutで1秒待ったのち、文字列を引数にonFulfilledを呼び出し非同期処理の成功を通知します。
  4. onFulfilledは渡された値setTimeout completedconsole.logで出力します。

3-1. リゾルバ関数

Promiseを使って実行したい非同期処理は、 Promiseオブジェクトのコンストラクタに引数として渡されるリゾルバ関数に記述します。

リゾルバ関数はonFulfilledonRejectedの2つの関数を引数として受け取ります。

 function(onFulfilled(value), onRejected(error))

3-2. onFulfilled

onFulfilled引数は非同期処理が成功した際に呼び出す関数です。onFulfilledを呼び出すと次の.thenに成功が通知されます。

onFulfilledvalue引数には任意の値を渡します。valueは省略可能です。

onFulfilledに渡したvalueは、.thenメソッドの引数として渡されるonFulfilledコールバック関数呼び出しの際に引数として渡されます。

3-3. onRejected

onRejected引数は非同期処理が失敗した際に呼び出す関数です。onRejectedを呼び出すと次の.thenに失敗が通知されます。

onRejectederror引数には任意の値を渡します。errorは省略可能です。通常errorにはErrorオブジェクトを渡します。

onRejectedに渡したerrorは、.thenメソッドの引数として渡されるonRejectedコールバック関数呼び出しの際に引数として渡されます。

以下は上記のsecond_promise.jssetTimeoutコールバック関数を書き換えてエラーを通知するようにした例です。

fs.readFileAsync('second_promise.js', 'utf-8').then(function(value) {
  console.log(value);

  return new Promise(function(onFulfilled, onRejected) {
    setTimeout(function() {
      //onFulfilled('setTimeout completed');
      onRejected(new Error('setTimeout failed'));
    }, 1000);
  });

}).then(function(value) {
  console.log(value);
}, function(error) {
  console.error(error);
});

onRejectedによりエラーが通知されると、onRejectedが無効にされている.thenをスキップし、最初に現れたonRejectedが呼び出されます。例えば以下のようにonFulfilledしか持たない.thenを追加した場合も、エラーが通知された場合は最初のonRejectedが呼び出されます。

      onRejected(new Error('setTimeout failed'));
    }, 1000);
  });

}).then(function(value) {
  // 足した
  console.log(value);
}).then(function(value) {
  console.log(value);
}, function(error) {
  console.error(error);
});

また、リゾルバ関数中で例外が送出された際は、例外オブジェクトを引数にonRejectedが呼び出されたかのように振る舞います。setTimeoutのコールバック関数を以下のように書き換えても上の例と同じ挙動になりますが、エラーが明示的ならonRejectedを呼び出して通知した方が良いでしょう。

    setTimeout(function() {
      //onFulfilled('setTimeout completed');
      //onRejected(new Error('setTimeout failed'));
      throw new Error('setTimeout failed');
    }, 1000);

4. .thenメソッド呼び出しの省略

以上がPromiseの基本的な使い方です。ここまでをまとめると以下のようになります。

  1. Promiseオブジェクトのコンストラクタに渡すリゾルバ関数で非同期処理を記述しonFulfilledで成功を、onRejectedで失敗を通知します。
  2. Promiseオブジェクトから値を取得する際は.thenメソッドを呼び出します。成功した際の値の取得はonFulfilledで、失敗した際のエラーの取得はonRejectedで取得します。

Promiseオブジェクトの.thenメソッドの呼び出しを省略した場合、単に非同期処理が実行されます。

var Promise = require('bluebird');

new Promise(function(onFulfilled, onRejected) {
  setTimeout(function() {
    console.log('setTimeout finish');
    onFulfilled(1);
  }, 1000);
});

このスクリプトを実行すると、1秒待ってからsetTimeout finishと表示されます。

リゾルバ関数はPromiseのコンストラクタ中で即実行され、.thenメソッドの呼び出しをもって実行される訳では無い点に留意してください。また、同じ理由から、.thenonFulfilledonRejectedがそのままリゾルバ関数に渡される訳ではありません。(そのまま渡すような実装にしてしまうと、失敗した際に初めて現れたonRejectedにエラーを渡すという処理が実装できません。)

5. Promiseオブジェクトのその他のメソッド

Promiseオブジェクトには.thenメソッドの他に、インスタンスメソッドとして.catchが、クラスメソッドとして Promise.resolvePromise.rejectPromise.allPromise.raceがあります。

5-1. .catch メソッド

.catch メソッドは、.then(undefined, function(error)) のエイリアスで、エラー通知のみ受け取ります。

var Promise = require('bluebird');

new Promise(function(onFulfilled, onRejected) {
  onRejected(new Error("ERROR"));
}).then(function(value) {
  console.log(value);
}).catch(function(error) {
  console.error(error);
});

new Promiseのリゾルバ関数でエラーが通知されると、次の.thenにはonRejectedが存在しないためそのまま次の.catchへ制御が移り、.catchでErrorオブジェクトがconsole.errorにより出力されます。(Promiseではエラーが通知されると、メソッドチェインされた.thenonRejectedが表れるまでonFulfilledはスキップされますので、上の例のconsole.log(value);は実行されません。)

5-2. Promise.resolveクラスメソッド

Promise.resolveクラスメソッドは引数で渡された値で成功するPromiseオブジェクトを返します。

var Promise = require('bluebird');

Promise.resolve("success").then(function(value) { console.log(value); });

このコードを実行するとsuccessと出力されます。

メソッドチェインの先頭のダミーオブジェクトに置きたい場合、Promise.resolveクラスメソッドがよく使用されます。

5-3. Promise.rejectクラスメソッド

Promise.rejectクラスメソッドは引数で渡された値で失敗する(エラー通知する)Promiseオブジェクトを返します。

var Promise = require('bluebird');

Promise.reject("failed").then(function(value) {
  console.log(value);
}).catch(function(error) {
  console.error(error);
});

このコードを実行するとfailedと出力されます。

5-4. Promise.allクラスメソッド

Promise.allクラスメソッドはPromiseオブジェクトを要素とするIterableオブジェクト(JavaScript では配列)を引数に受け取り、すべての非同期処理が成功したときに成功するPromiseオブジェクトを返します。Promise.allは非同期処理が1つでも失敗すると即失敗します。

var Promise = require('bluebird');

var p1 = new Promise(function(onFulfilled, onRejected) {
  setTimeout(function() {
    onFulfilled(1);
  }, 1000);
});

var p2 = new Promise(function(onFulfilled, onRejected) {
  setTimeout(function() {
    onFulfilled(2);
  }, 2000);
});

Promise.all([p1, p2]).then(function(value) { console.log(value); });

このコードを実行すると2秒後に[ 1, 2 ]が出力されます。p1p2は並列実行されます。

5-5. Promise.raceクラスメソッド

Promise.raceクラスメソッドはPromiseオブジェクトを要素とするIterableオブジェクト(JavaScript では配列)を引数に受け取り、最初に成功または失敗したPromiseオブジェクトを返します。

var Promise = require('bluebird');

var p1 = new Promise(function(onFulfilled, onRejected) {
  setTimeout(function() {
    onFulfilled(1);
  }, 1000);
});

var p2 = new Promise(function(onFulfilled, onRejected) {
  setTimeout(function() {
    onFulfilled(2);
  }, 2000);
});

Promise.race([p1, p2]).then(function(value) { console.log(value); });

このコードを実行すると1秒後に1が出力されます。Promise.allと同じくp1p2は並列実行されます。

6. Promiseとasync.jsの変換

Node.jsで非同期処理を実装する場合(単純なコールバックを除いて)ポピュラーな手法は async.js でしょう。

非同期処理にasync.jsを使っているが、使用したいモジュールがPromiseしか返さないのでasync.jsで書かれたコードをPromiseに置き換えたいといったシーンもあると思います。

ここからPromiseによる非同期処理とasync.jsによる非同期処理を比較し、async.jsをPromiseに変換する方法を検討していきます。

6-1. .thenメソッドチェインとasync.waterfall

.thenメソッドチェインでは非同期処理を順次実行し、成功した場合次の.thenに値を渡します。async.jsでこの挙動と似た処理を行うのはasync.waterfallです。async.seriesも似た様な挙動ですが、非同期処理の値を次の非同期処理に渡すという意味でasync.waterfallの方がより近い処理と言えます。

以下の2つのコードは、同じ処理をasync.waterfall.thenメソッドチェインで書いたものです。どちらも実行すると2を出力します。

// async.waterfall 版
var async = require('async');

async.waterfall([
  function(callback) {
    setTimeout(function() { callback(null, 1); }, 1000);
  },
  function(value, callback) {
    setTimeout(function() { callback(null, 2); }, 1000);
  },
], function(error, value) {
  if (error) {
    console.error(error);
    return;
  }
  console.log(value);
});
// .then メソッドチェイン版
var Promise = require('bluebird');

new Promise(function(onFulfilled, onRejected) {
  setTimeout(function() { onFulfilled(1); }, 1000);
}).then(function(value) {
  return new Promise(function(onFulfilled, onRejected) {
    setTimeout(function() { onFulfilled(2); }, 1000);
  });
}).then(function(value) {
  console.log(value);
}).catch(function(error) {
  console.error(error);
});

async.waterfall.thenメソッドチェインに置き換える場合、async.waterfallではエラーが返されると以降の処理はすべてスキップされコールバックが呼び出されるのに対して、.thenメソッドチェインではエラーが通知されると次のonRejectedが実行されるまでonFulfilledがスキップされる(.thenの評価自体はスキップされない)点に注意してください。

具体的には、async.waterfall版のコードからエラーを発生させるようにするとcallback(null, 2);を含む2つ目の処理は実行されませんが、

var async = require('async');

async.waterfall([
  function(callback) {
    setTimeout(function() { callback(new Error('error')); }, 1000);
  },
  // 次の関数は実行されない
  function(value, callback) {
    setTimeout(function() { callback(null, 2); }, 1000);
  },
], function(error, value) {
  if (error) {
    console.error(error);
    return;
  }
  console.log(value);
});

.thenメソッドチェイン版のコードからエラーを発生させるようにするとエラー後の.thenonFulfilledonRejectedが現れるまでスキップされます。実行される処理は async.waterfall版と同じですが、コードの意味は異なります。

var Promise = require('bluebird');

new Promise(function(onFulfilled, onRejected) {
  setTimeout(function() { onRejected(new Error('error')); }, 1000);
})
// この .then のリゾルバ関数が返す Promise オブジェクトは
// onFulfilled しか持たないので onRejected は呼ばれず次の.thenへ制御が移る
.then(function(value) {
  return new Promise(function(onFulfilled, onRejected) {
    setTimeout(function() { onFulfilled(2); }, 1000);
  });
})
// この .then のリゾルバ関数は値を返すので(return を省略すると undefined が返る)
// onRejected は呼ばれず次の.thenへ制御が移る
.then(function(value) {
  console.log(value);
// .catch で onRejected をトラップ
}).catch(function(error) {
  console.error(error);
});

6-2. Promise.allクラスメソッドとasync.parallel

Promise.allクラスメソッドはIterableに含まれるすべてのPromiseを実行しすべて成功すると成功するPromiseを返します。async.jsでこの挙動と似た処理を行うのはasync.parallelです。

以下の2つのコードは、同じ処理をasync.parallelPromise.allで書いたものです。どちらも実行すると[ 1, 2 ]を出力します。

var async = require('async');

async.parallel([
  function(callback) {
    setTimeout(function() { callback(null, 1); }, 1000);
  },
  function(callback) {
    setTimeout(function() { callback(null, 2); }, 1000);
  },
], function(error, value) {
  if (error) {
    console.error(error);
    return;
  }
  console.log(value);
});
var Promise = require('bluebird');

Promise.all([
  new Promise(function(onFulfilled, onRejected) {
    setTimeout(function() { onFulfilled(1); }, 1000);
  }),
  new Promise(function(onFulfilled, onRejected) {
    setTimeout(function() { onFulfilled(2); }, 1000);
  })
]).then(function(value) {
  console.log(value);
}).catch(function(error) {
  console.error(error);
});

6-3. Promise.raceクラスメソッドと対応するメソッドはasync.jsにない

Promise.raceクラスメソッドはIterableオブジェクトを引数に受け取り最初に完了したPromiseオブジェクトを返します。厳密にこの挙動に対応するメソッドはasync.jsにはありません。

async.detectasync.parallelなどを使って似た様なコードを書く事もできますが、あまり分かりやすいコードではないので省略します。

6-4. async.each async.eachSeriesの代替コードをPromiseで実装する

async.jsには多くのメソッドがあり、Promiseに存在していない機能も多数あります。その中でもasync.eachasync.eachSeriesは利用頻度が高いと思われますので、Promiseでの代替コードを例示します。

6-5. async.eachPromise.allで置き換える

async.eachは、以下のプロトタイプを持ちます。

 each(arr, iterator, callback)

配列arrのすべての要素に関数iteratorを適用し並列実行します。すべてのiteratorの処理が終わるとcallbackが実行されます。

var async = require('async');

async.each([1, 2, 3, 4, 5], function(num, callback) {
  setTimeout(function() {
    console.log(num);
    callback();
  }, 1000);
}, function(err) {
  console.log('Finish');
});

上のコードは1から5の数字を1秒待ってから出力します。iteratorは並列起動されますので、数字の出力されるタイミングはほぼ同じになります。

これと同じ処理をPromise.allで書くと以下のようになります。

var Promise = require('bluebird');

Promise.all([1, 2, 3, 4, 5].map(function(num) {
  return new Promise(function(onFulfilled, onRejected) {
    setTimeout(function() {
      console.log(num);
      onFulfilled(num);
    }, 1000);
  });
})).then(function(value) {
  console.log('Finish');
});

数値の配列をPromiseオブジェクトの配列へと変換し、Promise.allに渡すとasync.eachと同様の処理となります。

なお、Promise.all.thenonFullfielledに渡された値の配列を返しますので、async.mapを使った以下のコードも上のコードで代替できます。

var async = require('async');

async.map([1, 2, 3, 4, 5], function(num, callback) {
  setTimeout(function() {s
    console.log(num);
    callback(null, num);
  }, 1000);
}, function(err, results) {
  console.log('Finish');
});

6-6. async.eachEachPromise.allで置き換える

async.eachSeriesは、逐次処理版のasync.eachで、eachと同じプロトタイプを持ちます。

 eachSeries(arr, iterator, callback)

配列arrのすべての要素に関数iteratorを逐次適用し実行します。すべてのiteratorの処理が終わるとcallbackが実行されます。

var async = require('async');

async.eachSeries([1, 2, 3, 4, 5], function(num, callback) {
  setTimeout(function() {s
    console.log(num);
    callback();
  }, 1000);
}, function(err) {
  console.log('Finish');
});

上のコードは1秒待ってから1から5の数字を出力していきます。

これと同じ処理をPromiseで書くと以下のようになります。

var Promise = require('bluebird');

[1, 2, 3, 4, 5].reduce(function(promise, num) {
  return promise.then(function(value) {
    return new Promise(function(onFulfilled, onRejected) {
      setTimeout(function() {
        console.log(num);
        onFulfilled(num);
      }, 1000);
    });
  });
}, Promise.resolve()).then(function(value) {
  console.log('Finish');
});

Promiseでの逐次実行はメソッドチェインになりますので、Array.reduceでメソッドチェインを作成する処理がasync.eachEachと同じ処理になります。Array.reduceの初期値にPromise.resolveの返す値を渡すことで、Array.reduceコールバック内で配列の最初の要素か判定する処理を省略しています。

なお、メソッドチェインは最終的に.thenonFullfielledに渡された値の配列を返しますので、async.mapSeriesを使った以下のコードも上のコードで代替できます。

var async = require('async');

async.mapSeries([1, 2, 3, 4, 5], function(num, callback) {
  setTimeout(function() {s
    console.log(num);
    callback(null, num);
  }, 1000);
}, function(err, results) {
  console.log('Finish');
});

6-7. Promise.promisifyAllを使用してasync.jsをPromiseに変換する

Promiseの実装としてbluebirdモジュールを使用している場合、Promise.promisifyAll を使用して、Promise版のasync.jsメソッドを使用することができます。

Promise.promisifyAllAsyncサフィックスを持つメソッドをPromise版メソッドとして追加します。上に例示したasync.eachのコードでPromiseを使用する場合はasync.eachAsyncを呼び出します。

var Promise = require('bluebird');
var async = require('async');
Promise.promisifyAll(async);

async.eachAsync([1, 2, 3, 4, 5], function(num, callback) {
  setTimeout(function() {
    console.log(num);
    callback();
  }, 1000);
}).then(function(value) {
  console.log('Finish');
});

7. リゾルバ関数、onFulfilledonRejectedの返す値について

最後に、リゾルバ関数やonFulfilledonRejectedからPromiseオブジェクト以外の値を返した場合について説明します。

7-1. リゾルバ関数等が値を返す場合

リゾルバ関数やonFulfilledからは以下のように値を返すこともできます。

var Promise = require('bluebird');

Promise.resolve(1).then(function() {
  return 2;
}).then(function(value) {
  console.log(value);
}).then(function(value) {
  console.log(value);
});

上のコードを実行すると2undefinedが出力されます。2Promise.resolve.thenのリゾルバ関数から返された値、undefinedはその次の.thenのリゾルバ関数から返された値です(JavaScriptは戻り値を省略するとundefinedを返します)。

要は上のコードは以下のコードと等価ということになります。

var Promise = require('bluebird');

Promise.resolve(1).then(function() {
  return Promise.resolve(2);
}).then(function(value) {
  console.log(value);
  return Promise.resolve(undefined);
}).then(function(value) {
  console.log(value);
});

リゾルバ関数等から値を返す、ということはPromiseが非同期実行されないことを意味します。Promiseは並列処理に関する仕組みですので、同期実行されるリゾルバ関数はあまり重要ではありませんが、return文が無いonFulfilledonRejectedは頻繁に出てきます。その際はundefinedが次のonFulfilledに渡されると覚えておくと混乱することが少ないと思います。

7-2. リゾルバ関数等がthenableオブジェクトを返す場合

thenableとは.thenメソッドを持つ任意のオブジェクトです。

var thenable =  {
  then: function(onFulfilled, onRejected) { }
};

リゾルバ関数やonFulfilledからthenableオブジェクトを返すと、PromiseはthenableオブジェクトをPromiseオブジェクトであるかのように扱います。

var Promise = require('bluebird');

Promise.resolve().then(function() {
  return {
    then: function(onFulfilled, onRejected) { onFulfilled(1); }
  }
}).then(function(value) {
  console.log(value);
});

thenableは特定のPromiseモジュールやPromise以外のthenableを扱うことができる非同期モジュールに依存しない形で非同期処理を実装したい場合に使用できます。特定のPromiseモジュールを使用する場合は、thenableを意識する必要は特にありません。

使用したいモジュール等がthenableを返す実装になっておりPromiseで処理したい場合は、Promise.resolveを実行するとPromiseオブジェクトに変換できます。もしくは.thenonFulfilledからthenableオブジェクトを返しても同じように処理されます。

var Promise = require('bluebird');

function thenable_return_func() {
  return {
    then: function(onFulfilled, onRejected) {
      console.log('thenable');
      setTimeout(function() {
        console.log('finish');
        onFulfilled(1);
      }, 1000);
    }
  };
}

// Promise.resolve には thenable を渡す
Promise.resolve(thenable_return_func())
// .then onFulfilled に thenable を返す関数を渡す
.then(thenable_return_func);

8. Promise関連リンク

最後にPromise関連資料へのリンクを掲載します。Promiseの詳細や理論的な部分に興味のある方はこちらをご覧ください。

Tokyo Otaku Mode からのお知らせ

Tokyo Otaku Modeでは非同期処理を極めたいNode.jsエンジニアを募集しています。
興味のある方はこちらからご応募ください。