JavaScriptで非同期関数を作成するにはどうすればよいですか?


143

このコードをチェックしてください:

<a href="#" id="link">Link</a>
<span>Moving</span>

$('#link').click(function () {
    console.log("Enter");
    $('#link').animate({ width: 200 }, 2000, function() {
         console.log("finished");            
    });    
    console.log("Exit");    
});

コンソールに表示されるように、「アニメーション」関数は非同期であり、イベントハンドラーブロックコードのフローを「フォーク」します。実際には :

$('#link').click(function () {
    console.log("Enter");
    asyncFunct();
    console.log("Exit");    
});

function asyncFunct() {
    console.log("finished");
}

ブロックコードの流れに従ってください!

function asyncFunct() { }この動作で自分を作成したい場合、javascript / jqueryでどのように作成できますか?私はを使用せずに戦略があると思います setTimeout()


jQueryのソースを
ご覧ください

.animate()mathodはコールバックを使用します。Animateは、アニメーションが完了するとコールバックを呼び出します。.animate()と同じ動作が必要な場合、必要なのはコールバックです(他のいくつかの操作の後に「メイン」関数によって呼び出されます)。「完全な」非同期関数(実行フローをブロックせずに呼び出される関数)が必要な場合は異なります。この場合、ほぼ0の遅延でsetTimeout()を使用できます。
Fabio Buda

@Fabio Buda:callback()が一種の非同期を実装する必要があるのはなぜですか?実際、jsfiddle.net
5H9XT / 9

実際、「コールバック」の後、setTimeoutを使用して「完全な」非同期メソッドを引用しました。他のコードの後に​​関数が呼び出される方法での疑似非同期としてのコールバックを意味しました:-)
Fabio Buda

回答:


185

真にカスタムな非同期関数を作成することはできません。最終的には、次のようなネイティブで提供されるテクノロジーを活用する必要があります。

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • File API、Web Database APIなどの一部のHTML5 API
  • サポートするテクノロジー onload
  • ...他の多くの

実際、アニメーションの場合、jQuery はを使用し setIntervalます。


1
私は昨日これを友人と話し合ったので、この答えは完璧です!私は非同期機能を理解して識別でき、JSで適切に使用できます。しかし、なぜ私たちがカスタムのものを実装できないのは、私には明らかではありません。これは(たとえばを使用して)どのように機能するかを知っているブラックボックスのようなものですが、setIntervalそれを開いてそれがどのように行われているかを確認することさえできません。あなたはたまたまこの主題についてもっと情報がありますか?
Matheus Felipe

2
@MatheusFelipeこれらの関数はJavaScriptエンジンの実装にネイティブであり、信頼できる唯一のものはHTML5タイマーなどの仕様であり、仕様に従って動作するブラックボックスの性質を信頼します。
スポーク

10
@MatheusFelipe youtu.be/8aGhZQkoFbQこのトピックに関してこれまでのところ最高の話...
Andreas Niedermair

一部の実装、特にNode.jsのサポートsetImmediate
Jon Surrell 2015年

どうかpromises。それは与えawaitableますか?
Nithin Chandran

69

タイマーを使用できます。

setTimeout( yourFn, 0 );

yourFn関数への参照はどこですか)

または、Lodashの場合

_.defer( yourFn );

func現在の呼び出しスタックがクリアされるまでの呼び出しを延期します。追加の引数は、func呼び出されたときに提供されます。


3
これは機能しません。キャンバスに描画する私のJavaScript関数がUIを応答させ続けます。
gab06 2015

2
@ gab06-キャンバス描画機能は、それ自体の正当な理由でブロックされていると思います。多くの小規模なものにし、タイマーと、それらの各1たinvokeその作用を分割:あなたはインターフェイスこの方法は、など、あなたのマウスクリックに応答しないことがわかります
マルコFaustinelli

リンクが壊れています。
-JulianSoto

1
@JulianSoto固定
サイムVIDAS

1
の最小時間setTimeoutは、HTML5仕様では4ミリ秒です。0を指定しても、その最小時間がかかります。しかし、そうです、それは関数延期としてうまく機能します。
hegez

30

ここであなたは簡単な解決策を持っています(他のそれについて書いてくださいhttp://www.benlesh.com/2012/05/calling-javascript-function.html

そしてここにあなたは上記の準備ができた解決策があります:

function async(your_function, callback) {
    setTimeout(function() {
        your_function();
        if (callback) {callback();}
    }, 0);
}

テスト1(「1 x 2 3」または「1 2 x 3」または「1 2 3 x」を出力する場合があります):

console.log(1);
async(function() {console.log('x')}, null);
console.log(2);
console.log(3);

テスト2(常に「x 1」を出力します):

async(function() {console.log('x');}, function() {console.log(1);});

この関数はタイムアウト0で実行されます-非同期タスクをシミュレートします


6
TEST 1は実際には「1 2 3 x」のみを出力でき、TEST 2は毎回「1 x」を出力することが保証されています。TEST 2で予期しない結果が生じる理由は、console.log(1)が呼び出され、出力(undefined)が2番目の引数としてに渡されるためasync()です。テスト1の場合、JavaScriptの実行キューを完全には理解していないと思います。console.log()同じスタックで発生する各呼び出しのため、x最後にログに記録されることが保証されています。私は誤った情報のためにこの回答に反対票を投じますが、十分な担当者がいません。
Joshua Piccari 2015年

1
@Joshua:それはのように書き込みテスト2に意味@fiderようです:async(function() {console.log('x')}, function(){console.log(1)});
nzn

はい、@ nznと@Joshuaを意味しましたTEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});-私はすでにそれを修正しました
fider

テスト2の出力は、async(function(){setTimeout(()=> {console.log( 'x');}、1000)}、function(){console.log(1);});で1 xです。
Mohsen

10

これは、別の関数を取り込んで、非同期で実行するバージョンを出力する関数です。

var async = function (func) {
  return function () {
    var args = arguments;
    setTimeout(function () {
      func.apply(this, args);
    }, 0);
  };
};

これは、非同期関数を作成する簡単な方法として使用されます。

var anyncFunction = async(function (callback) {
    doSomething();
    callback();
});

これは@fiderの回答とは異なります。これは、関数自体が独自の構造を持ち(コールバックが追加されておらず、関数にすでに含まれている)、使用可能な新しい関数を作成するためです。


setTimeoutはループ内で使用できません(別個の引数を使用して同じ関数を数回呼び出します)
user2284570 2016年

@ user2284570それがクロージャの目的です。(function(a){ asyncFunction(a); })(a)
2016年

1
IIRC、あなたはまた、閉鎖なしでこれを達成することができました:setTimeout(asyncFunction, 0, a);
Swivel

1
非同期とは、バックグラウンドで実行されており、メインスレッドと並行している場合、これは本当に非同期ではありません。これにより、process.nextTickへの実行が遅延します。関数内にあるコードはすべて、メインスレッドで実行されます。関数がPIを計算するように設定されている場合、アプリはタイムアウトの有無にかかわらずフリーズします。
Mike M

1
なぜこの答えが支持されているのか理解できません。これをコードに入れると、プログラムは関数が終了するまでブロックします。関数が完了すると、それ本来実行すべきではありません。
マーティンArgerami


6

遅くpromisesなりましたが、ES6での導入後に使用する簡単なソリューションを示すために、非同期呼び出しをはるかに簡単に処理します。

新しいプロミスで非同期コードを設定します。

var asyncFunct = new Promise(function(resolve, reject) {
    $('#link').animate({ width: 200 }, 2000, function() {
        console.log("finished");                
        resolve();
    });             
});

resolve()非同期呼び出しが終了したときに設定することに注意してください。
次に、非同期呼び出し.then()がpromise 内で終了した後に実行するコードを追加します。

asyncFunct.then((result) => {
    console.log("Exit");    
});

以下がその抜粋です。

$('#link').click(function () {
    console.log("Enter");
    var asyncFunct = new Promise(function(resolve, reject) {
        $('#link').animate({ width: 200 }, 2000, function() {
            console.log("finished");            	
            resolve();
        }); 			
    });
    asyncFunct.then((result) => {
        console.log("Exit");    
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#" id="link">Link</a>
<span>Moving</span>

またはJSFiddle



3

パラメータを使用して非同期関数の最大数を規制したい場合は、私が作成した単純な非同期ワーカーを使用できます。

var BackgroundWorker = function(maxTasks) {
    this.maxTasks = maxTasks || 100;
    this.runningTasks = 0;
    this.taskQueue = [];
};

/* runs an async task */
BackgroundWorker.prototype.runTask = function(task, delay, params) {
    var self = this;
    if(self.runningTasks >= self.maxTasks) {
        self.taskQueue.push({ task: task, delay: delay, params: params});
    } else {
        self.runningTasks += 1;
        var runnable = function(params) {
            try {
                task(params);
            } catch(err) {
                console.log(err);
            }
            self.taskCompleted();
        }
        // this approach uses current standards:
        setTimeout(runnable, delay, params);
    }
}

BackgroundWorker.prototype.taskCompleted = function() {
    this.runningTasks -= 1;

    // are any tasks waiting in queue?
    if(this.taskQueue.length > 0) {
        // it seems so! let's run it x)
        var taskInfo = this.taskQueue.splice(0, 1)[0];
        this.runTask(taskInfo.task, taskInfo.delay, taskInfo.params);
    }
}

次のように使用できます。

var myFunction = function() {
 ...
}
var myFunctionB = function() {
 ...
}
var myParams = { name: "John" };

var bgworker = new BackgroundWorker();
bgworker.runTask(myFunction, 0, myParams);
bgworker.runTask(myFunctionB, 0, null);

2
Function.prototype.applyAsync = function(params, cb){
      var function_context = this;
      setTimeout(function(){
          var val = function_context.apply(undefined, params); 
          if(cb) cb(val);
      }, 0);
}

// usage
var double = function(n){return 2*n;};
var display = function(){console.log(arguments); return undefined;};
double.applyAsync([3], display);

他のソリューションと根本的に異なるわけではありませんが、私のソリューションはいくつかの追加の素晴らしいことをしていると思います:

  • 関数へのパラメータを許可します
  • 関数の出力をコールバックに渡します
  • それFunction.prototypeを呼び出すためのより良い方法を許可するために追加されます

また、組み込み関数Function.prototype.applyとの類似性は私には適切なようです。


1

@pimvdbによるすばらしい回答の隣にあり、不思議なことに、async.jsは真に非同期の関数も提供していません。これは、ライブラリのメインメソッドの(非常に)削除されたバージョンです。

function asyncify(func) { // signature: func(array)
    return function (array, callback) {
        var result;
        try {
            result = func.apply(this, array);
        } catch (e) {
            return callback(e);
        }
        /* code ommited in case func returns a promise */
        callback(null, result);
    };
}

そのため、関数はエラーから保護し、処理するためにコールバックに正常に渡しますが、コードは他のJS関数と同じように同期しています。


1

残念ながら、JavaScriptは非同期機能を提供していません。シングルスレッドでのみ機能します。しかし、最新のブラウザのほとんどはWorkersを提供します。これは、バックグラウンドで実行され、結果を返すことができる2番目のスクリプトです。それで、非同期呼び出しごとにワーカーを作成する関数を非同期で実行すると便利だと思う解決策に到達しました。

以下のコードには、 バックグラウンドで呼び出す関数含まれていますasync

Function.prototype.async = function(callback) {
    let blob = new Blob([ "self.addEventListener('message', function(e) { self.postMessage({ result: (" + this + ").apply(null, e.data) }); }, false);" ], { type: "text/javascript" });
    let worker = new Worker(window.URL.createObjectURL(blob));
    worker.addEventListener("message", function(e) {
        this(e.data.result);
    }.bind(callback), false);
    return function() {
        this.postMessage(Array.from(arguments));
    }.bind(worker);
};

これは使用例です:

(function(x) {
    for (let i = 0; i < 999999999; i++) {}
        return x * 2;
}).async(function(result) {
    alert(result);
})(10);

これは、for巨大な数でa を反復して非同期性の実証として時間をかける関数を実行し、渡された数の2倍を取得します。このasyncメソッドはfunction、必要な関数をバックグラウンドで呼び出すと、asyncコールバックのパラメーターとしてreturn固有のパラメーターで提供されるを提供します。したがって、コールバック関数でalertは結果が返されます。


0

MDNは、「これ」を維持するsetTimeoutの使用に関する良い例を持っています。

次のように:

function doSomething() {
    // use 'this' to handle the selected element here
}

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.