2013-10-29 15 views
5

関数を受け取り、その関数の "async-style"バージョンを返す汎用ラッパーを書くのが大好きですIFこれは非同期ではありませんでした。関数を非同期スタイルに調和させる汎用ラッパー

問題は、呼び出しが同期か非同期かを簡単に知ることができないことです。だから...これは基本的に「できない」。右?

(ラッパーは非同期スタイルに同期機能を調和し、単独の非同期機能をLEAVE必要があることに注意してください)

var wrapper = function(fn){ 

    return function(){ 
     var args = Array.prototype.splice.call(arguments, 0); 

     var cb = args[ args.length - 1 ]; 

     // ?!?!?!?!? 
     // I cannot actually tell if `fn` is sync 
     // or async, and cannot determine it!  

     console.log(fn.toString()); 
    } 
} 

var f1Async = wrapper(function(arg, next){ 
    next(null, 'async' + arg); 
}) 

var f2Sync = wrapper(function(arg){ 
    return 'sync' + arg; 
}) 


f1Async("some", function(err, ret){ 
    console.log(ret); 
}); 


f2Sync("some other", function(err, ret){ 
    console.log(ret); 
}); 
+1

多くの人がノードのコールバック地獄について文句を言っています。コールバックをあまりにも愛していて、関数を同期させるためにコールバックを追加したい人がいるのは興味深いでしょう:) – Dan

+0

最後の引数のパラメータ名を取得するのは興味深いかもしれません。引数をチェックして、標準のコールバックパターンを満たしているかどうかを確認できます。 http://stackoverflow.com/questions/1007981/how-to-get-function-parameter-names-values-dynamically-from-javascript – Dan

+0

@Plato明示的に渡さずに外部から関数の引数をチェックする方法を教えてください。関数の引数を渡す必要があります。どう思いますか ? – user568109

答えて

1

javascriptでは、関数の最後の引数が関数かどうかを確認する方法はありません。javascriptでは引数の型を定義しないためです。

私のソリューションは、関数内のパラメータのリストを取得し、RegExpを使用して、そのパラメータが関数として使用されているかどうかを調べることによって機能します。また、コールバックが直接使用されていない場合(コールバックを他のものに渡すなど)、コールバックと見なされる引数名のリストがあります。

そして、コードは次のとおりです。

var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 
var CALLBACK_NAMES = [ "next", "done", "callback", "cb"]; 

function getParamNames(func) { 
    var fnStr = func.toString().replace(STRIP_COMMENTS, '') 
    var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(/([^\s,]+)/g) 
    if(result === null) 
    result = [] 
    return result 
} 

function checkIfParamIsFunction(func, paramName){ 
    var fnStr = func.toString(); 
    if (fnStr.replace(new RegExp("(" + paramName + "\s*\([A-Za-z0-9,\.]*\)?!{|" + paramName + ".apply\([A-Za-z0-9,\.]*\)|" + paramName + ".call\([A-Za-z0-9,\.]*\))", ""), "{<._|/}") != fnStr) { // Remove All Calls to the arg as a function, then check to see if anything was changed. 
    return true; 
    } else { 
    return false; 
    } 
} 


function makeAsync(func) { 
    var paramNames = getParamNames(func); 
    if (checkIfParamIsFunction(func, paramNames[paramNames.length - 1]) 
    || CALLBACK_NAMES.indexOf(paramNames[paramNames.length - 1]) != -1) { 
    // Function Is Already async 
    return func; 
    } else { 
    return function() { 
     var args = Array.prototype.slice.call(arguments); 
     var cb = args.pop(); 
     cb(func.apply(this, args)); 
    } 
    } 
} 

function test1(a){ 
    return (a+' test1'); 
}; 

function test2(a, callback){ 
    return callback(a+' test2') 
}; 

function main(){ 
    var tested1 = makeAsync(test1); 
    var tested2 = makeAsync(test2); 
    tested1('hello', function(output){ 
    console.log('holy shit it\'s now async!!'); 
    console.log(output); 
    }); 
    tested2('world', function(output){ 
    console.log('this one was already async tho'); 
    console.log(output); 
    }); 
} 

main(); 

単にmakeAsync(function)を呼び出し、それが非同期機能を返します。これは、function.applyまたは.callを使用すると機能します。

+0

最初の' if/else'ブロックの後に余分な括弧があり 'checkIfParamIsFunction'が未定義で、' paramName'は3行目で定義されません。あなたはトップの近くに関数定義がないのですか? – Plato

+0

@プラート、はい、ありがとう、 –

+0

プラトン、あなたは答えに満足していますか?私はそれを正しいものとして受け入れるか?賞金は4時間で失効します... – Merc

1

それは単純に実行することはできません。物語の終わり。

2

関数の受け入れられた引数が何であるかを知ることができないので、コールバックを受け取るかどうかを知ることはできません。

1

しかし、これは答えではありませんが、良い方法です。私はブラウザベースのJavaScriptの例を提供していますが、Nodeでも同じクラスを使用できます。

この問題を解決するために、約束が開発されました。しかし、私たちは以下のようにpromiseの修正版を使用します。

function AtomPromise(f) 
{ 
    // Success callbacks 
    this.sc = []; 
    // Error callbacks 
    this.ec = []; 
    this.i = f; 
    this.state = 'ready'; 
} 

AtomPromise.prototype ={ 
    finish: function(s,r) { 
     this.result = r; 
     var c = s ? this.sc : this.ec; 
     this.state = s ? 'done' : 'failed' ; 
     for(var i=o ; i< c.length; i++){ 
      c[i](this); 
     } 
    }, 
    invoke: function(f) { 
     this.state = 'invoking'; 
     this.i(this); 
    }, 
    then: function(f) { 
     this.sc.push(f); 
    }, 
    failed: function(f){ 
     this.ec.push(f); 
    }, 
    value: function(v) {  
     if(v !== undefined) this.result = v; 
     return this.result; 
    }, 
    pushValue: function(v) { 
     var _this = this; 
     setTimeout(100, function() { 
     _this.finish(true, v); 
     }); 
    } 
} 



//wrap jQuery AJAX 
AtomPromise.ajax = function(url, options) { 
    return new AtomPromise(function (ap){ 
     $.ajax(url, options) 
     .then(function(r){ ap.finish(true, r); }) 
     .failed(function(){ ap.finish(false, arguments) }); 
    }) ; 
} 

//Wrape sync function 
AtomPromise.localStorage = function(key) { 
    return new AtomPromise(function (ap){ 
     var v = localStorage[key]; 
     ap.pushValue(v); 
    }) ; 
} 



// Calling Sequence 

AtomPromise.ajax('Url').then(function(ap) { 
    alert(ap.value()); 
}).invoke(); 

AtomPromise.localStorage('Url').then(function(ap) { 
    alert(ap.value()); 
}).invoke(); 

両方の機能が非同期になりました。プッシュバリューメソッドは、setTimeoutを通じて結果ルートを作成し、さらに非同期呼び出しを行います。

これは、WebアトムJSで非同期コードを単一の属性にまとめるために使用され、非同期コールバック・ヘルを取り除くことができる1つのパターンに従うことで実現します。 http://webatomsjs.neurospeech.com/docs/#page=concepts%2Fatom-promise.html

免責事項:私はWeb Atoms JSの著者です。

+0

下部の呼び出しシーケンスはきれいに見えます。なぜ 'setTimeout'を使う必要がありますか?あなたは単に 'ap.pushValue(v)'の代わりに 'ap.finish(true、v)'を呼び出すことができませんでしたか?また、ミリ秒の引数は秒でなければなりません: 'setTimeout(fn、100)' – Plato

+0

私はpushValueを使用している理由は、それが真に非同期にすることであると説明しました。フィニッシュは同じスタックでコールしますが、それはあなた次第です。 –