[JavaScript] 非同期処理をなんとかするPromiseを使いたい

2014/12/12

こんにちは。きんくまです。

SQLiteにNodeからデータを入れてるんですけど、非同期処理が多くなってなんとかしたいと思いました。
で、いろいろと調べてみました。

>> [JavaScript] 非同期処理のコールバック地獄から抜け出す方法
>> コールバック……駆逐してやる…この世から…一匹…残らず!!
>> Node.js 0.12 では yield が使えるのでコールバック地獄にサヨナラできる話
>> Node.jsのコールバック地獄をPromiseやGeneratorを使って解消する

どうやら今後はyieldキーワードと一緒に使うgeneratorsが本命の様子ですね。

でも現在は環境として限定されているみたいなので(Node v0.11.2以降)、Promiseを使うことにしました。
Promiseについて調べてました。

>> JavaScript Promiseの本
>> JavaScript Promises
>> You’re Missing the Point of Promises

サンプルコード

TypeScriptなので、関数が ()=>{} になってます。

function myPromise(msg){
    return new Promise((resolve, reject)=>{
        console.log('start ', msg);
        setTimeout(()=>{
            if(msg == 'first' || msg == 'second'){
                resolve(msg);
            }else{
                reject(new Error('This is error message of ' + msg));
                //throw new Error('This is error message of ' + msg);  //※1
            }
        }, 300);
    });
}

myPromise('first')
    .then((msg)=>{
        console.log('success', msg);
        return myPromise('second');
    }).then((msg)=>{
        console.log('success', msg);
        return myPromise('third');
    }).then((msg)=>{
        console.log('success', msg);
        return myPromise('fourth');
    }).then((msg)=>{
        console.log('success', msg);
        return myPromise('fifth');
    }).catch((err)=>{
        console.log(err);
    }).then(()=>{
        console.log('acync complete!!');
    });

出力

start  first
success first
start  second
success second
start  third
[Error: This is error message of third]
acync complete!!

非同期処理内でresolveが呼ばれると、使う側の次のthenが呼ばれます。
非同期処理内でrejectが呼ばれると、もっとも近いcatchまでthenをスキップしてから呼び出されます。
上の例だと、fourth, fifthが呼ばれずに third のエラーが吐き出されてます。
で、catchの処理をしてからまた、次のthenが呼ばれる。という感じです。

サンプルコード内の※1では非同期処理内でthrowがコメントアウトしてあります。もしrejectを使わないでそちらを使うとこんな出力になりました。

start  first
success first
start  second
success second
start  third
/path/to/example.js:89
                throw new Error('This is error message of ' + msg);
                      ^
Error: This is error message of third
    at null._onTimeout (/path/to/example.js:89:23)
    at Timer.listOnTimeout (timers.js:133:15)

Nodeの処理系だけなのかどうかわからないのですが、さきほどは最後の acync complete!! のメッセージが表示されたのですが、今回はここで止まってしましました。Promise内ではrejectする方がいいみたいです。

>> 4.3. throwしないで、rejectしよう

とりあえずはこれでなんとかなりそうです。

おまけ1 jQUeryのDeferred

非同期処理といえば jQueryのDeferredもできますね。で、調べてるときにjQueryのDeferredはPromiseとはちょっと違うみたいなことが書いてあったので調べてみました。

サンプルコード

function myDef(msg){
    var d = $.Deferred();
    console.log('setup', msg);
    setTimeout(function(){
        if(msg == 1 || msg == 2){
            d.resolve('hello ' + msg);
        }else{
            d.reject('error');
        }
    }, 300);
    return d.promise();
}

myDef(1)
.then(function(msg){
   console.log(msg);
   return myDef(2);
})
.then(function(msg){
    console.log(msg);
    return myDef(3);
})
.then(function(msg){
    console.log(msg);
    return myDef(4);
})
.then(function(msg){
    console.log(msg);
    return myDef(5);
})
.fail(function(msg){
    console.log('fail ', msg);
})
.then(function(){
    console.log('async complete');
});

jQueryの1.7.2で試してみると出力はこんな感じになりました。

setup 1
hello 1
setup 2
hello 1
setup 3
hello 1
setup 4
hello 1
setup 5
async complete

ちょっと意図したものと違うようです。エラーがうまく処理されないのと、thenの中のmsg引数が同じものをさしています。
このバージョンまでは、ちょっと違った書き方をする必要がありみたいです。

自分で前に書いた記事がありました。すっかり忘れてる!
>> [JavaScript] jQuery.Deferredで登録時に引数を持たせたい

それでもう少しバージョンをあげて1.8.3で試してみるとこんな感じになりました。

setup 1
hello 1
setup 2
hello 2
setup 3
fail  error

今度はわりと意図したようになってます。4,5の動作はスキップされてエラー処理までいってます。
Promiseの挙動と違うところはエラーがおきたあとに最後のthenが呼ばれてないところですかね。

公式のAPIを読むと、1.8からなんか変わったとのことなので、各所からPromiseの仕様がCommonJSのそれと違ってるとツッコミが入り、変更されたのかもしれない。

>> deferred.then()

>> こういうところとか

おまけ2 Pyramid of Doomに萌え

Callback Hell (コールバック地獄)
Pyramid of Doom (破滅 とか 死のピラミッド)

とかが出てきて、なんか中2病っぽい名前で萌えた。

Pyramid of Doomは、callbackのインデントが逆三角形になってて、視覚的にも萌えた。

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る