web-dev-qa-db-ja.com

非同期JavaScript関数を同期的に呼び出す

第一に、これは非同期呼び出しを何千行もの長さで非常に同期的なコードベースに改造することを意図的に間違った方法で行うという非常に特殊なケースですそれは正しい」それは私の存在のあらゆる繊維を傷つけますが、現実と理想はしばしば噛み合うことはありません。私はこれが吸うのを知っています。

OK、邪魔にならないように、次のことができるようにするにはどうすればよいですか。

function doSomething() {

  var data;

  function callBack(d) {
    data = d;
  }

  myAsynchronousCall(param1, callBack);

  // block here and return data when the callback is finished
  return data;
}

例(またはその欠如)はすべてライブラリやコンパイラを使用していますが、どちらもこの解決策には実行できません。私はそれをブロックする方法の具体例が必要です(例えばコールバックが呼ばれるまでdoSomething関数を残さないでください)。そのようなことがJSで可能であれば。

187
Robert C. Barth

「どのようにしたらよいのか、正しいことを言ってはいけません」

OK。 しかし、あなたは本当にそれを正しいやり方でやるべきです...あるいは何でも

「UIをフリーズさせずにブロックする方法の具体的な例が必要です。JSでそのようなことが可能な場合」

いいえ、UIをブロックせずに実行中のJavaScriptをブロックすることは不可能です。

情報が不足しているため、解決策を提示するのは困難ですが、1つの選択肢は、呼び出し側の関数にグローバル変数をチェックするためのポーリングを実行させ、その後コールバックでdataをグローバルに設定することです。

function doSomething() {

      // callback sets the received data to a global var
  function callBack(d) {
      window.data = d;
  }
      // start the async
  myAsynchronousCall(param1, callBack);

}

  // start the function
doSomething();

  // make sure the global is clear
window.data = null

  // start polling at an interval until the data is found at the global
var intvl = setInterval(function() {
    if (window.data) { 
        clearInterval(intvl);
        console.log(data);
    }
}, 100);

これはすべてdoSomething()を変更できると仮定しています。それがカードにあるかどうかはわかりません。

それが修正されることができるならば、私はなぜあなたがもう片方のコールバックから呼ばれるためにdoSomething()へコールバックを渡さないであろうかわかりません、しかし、私はトラブルに陥る前に私はやめます。 ;)


ああ、一体何だ。あなたはそれが正しく行われることを示唆する例を挙げました、それで私はその解決策を示すつもりです...

function doSomething( func ) {

  function callBack(d) {
    func( d );
  }

  myAsynchronousCall(param1, callBack);

}

doSomething(function(data) {
    console.log(data);
});

この例には非同期呼び出しに渡されるコールバックが含まれているため、正しい方法は、コールバックから呼び出される関数をdoSomething()に渡すことです。

もちろん、それだけがコールバックで行われているのであれば、funcを直接渡すだけです。

myAsynchronousCall(param1, func);
118
user1106925

JQueryの約束を見てください。

http://api.jquery.com/promise/

http://api.jquery.com/jQuery.when/

http://api.jquery.com/deferred.promise/

コードをリファクタリングします。

 
 var dfd = new jQuery.Deferred(); 
 
 
 function callBack(data){
 dfd.notify( [data]; [
]} 
 
 //非同期呼び出しを行う
 myAsynchronousCall(param1、callBack); 
 
関数do (data){
 //データを処理します... 
} 
 
 $ .when(dfd).then(doSomething); 
 
 
47
Matt Taylor

非同期機能 、機能 ES2017promices (非同期コードの特定の形式)とawaitキーワードを使用して、非同期コードを同期させて表示させることができます。また、以下のコード例では、async/await関数を表すasyncキーワードの前にあるキーワードfunctionに注目してください。 awaitキーワードは、asyncキーワードでプレフィックスされた関数内になければ機能しません。現在のところ、トップレベルの待機が機能しないことを意味する、これに対する例外はありません(トップレベルの待機は、関数外の待機を意味します)。 トップレベルのawaitに対する提案 がありますが。

ES2017は、2017年6月27日にJavaScriptの標準として承認されました(すなわち、確定しました)。非同期待機はすでにお使いのブラウザで機能する可能性がありますが、それでも機能しない場合は babel またはのようにしてください。 traceur 。 Chrome 55は非同期機能を完全にサポートしています。あなたがより新しいブラウザを持っているのであれば、あなたは以下のコードを試すことができるかもしれません。

ブラウザの互換性については、 kangaxのes2017互換性テーブル を参照してください。

これはdoAsyncという非同期待機関数の例です。この関数は、3秒間の休止を取り、開始時間から休止するたびに時間差を表示します。

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

async function doAsync () {
  var start = Date.now(), time;
  console.log(0);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
}

doAsync();

Awaitキーワードがpromise値の前に配置されている場合(この場合、promise値は関数doSomethingAsyncによって返された値です)、awaitキーワードは関数呼び出しの実行を一時停止しませんが、他の関数を一時停止しないで続行します。約束が解決するまで他のコードを実行する。約束が解決した後、それは約束の価値をアンラップするでしょう、そしてあなたは今そのラップされていない価値によって置き換えられているように待ち受けと約束の表現を考えることができます。

したがって、awaitは残りの行を実行する前にwaitを一時停止してから値をアンラップするため、配列で待機している時間差を収集して配列を出力する以下の例のようにforループや内部関数呼び出しで使用できます。

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this calls each promise returning function one after the other
async function doAsync () {
  var response = [];
  var start = Date.now();
  // each index is a promise returning function
  var promiseFuncs= [doSomethingAsync, doSomethingAsync, doSomethingAsync];
  for(var i = 0; i < promiseFuncs.length; ++i) {
    var promiseFunc = promiseFuncs[i];
    response.Push(await promiseFunc() - start);
    console.log(response);
  }
  // do something with response which is an array of values that were from resolved promises.
  return response
}

doAsync().then(function (response) {
  console.log(response)
})

Async関数自体がpromiseを返すので、私が上で行ったように、または別のasync await functionの中で、それをチェーン付きのpromiseとして使用できます。

Promise.all を使用して同時にリクエストを送信したい場合は、上記の関数はそれぞれのレスポンスを待ってから別のリクエストを送信します。

// no change
function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

// no change
function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this function calls the async promise returning functions all at around the same time
async function doAsync () {
  var start = Date.now();
  // we are now using promise all to await all promises to settle
  var responses = await Promise.all([doSomethingAsync(), doSomethingAsync(), doSomethingAsync()]);
  return responses.map(x=>x-start);
}

// no change
doAsync().then(function (response) {
  console.log(response)
})

プロミスが拒否する可能性がある場合は、try catchでラップするか、try catchをスキップしてエラーをasync/await関数のcatch呼び出しに伝播させることができます。特にNode.jsでは、プロミスエラーが処理されないように注意してください。以下は、エラーがどのように機能するのかを示すいくつかの例です。

function timeoutReject (time) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      reject(new Error("OOPS well you got an error at TIMESTAMP: " + Date.now()));
    }, time)
  })
}

function doErrorAsync () {
  return timeoutReject(1000);
}

var log = (...args)=>console.log(...args);
var logErr = (...args)=>console.error(...args);

async function unpropogatedError () {
  // promise is not awaited or returned so it does not propogate the error
  doErrorAsync();
  return "finished unpropogatedError successfully";
}

unpropogatedError().then(log).catch(logErr)

async function handledError () {
  var start = Date.now();
  try {
    console.log((await doErrorAsync()) - start);
    console.log("past error");
  } catch (e) {
    console.log("in catch we handled the error");
  }
  
  return "finished handledError successfully";
}

handledError().then(log).catch(logErr)

// example of how error propogates to chained catch method
async function propogatedError () {
  var start = Date.now();
  var time = await doErrorAsync() - start;
  console.log(time - start);
  return "finished propogatedError successfully";
}

// this is what prints propogatedError's error.
propogatedError().then(log).catch(logErr)

あなたが行くなら ここ あなたは今後のECMAScriptバージョンのための完成した提案を見ることができます。

これに代わるものとしてES2015(ES6)だけで使うことができるのはジェネレータ関数をラップする特別な関数を使うことです。ジェネレータ関数にはyieldキーワードがあり、これを使用してawaitキーワードを周囲の関数と複製することができます。 yieldキーワードとgenerator関数はもっと一般的な目的であり、async await関数がすることよりももっとたくさんのことができます。あなたが非同期を複製するために使用することができるジェネレータ関数ラッパーが欲しいなら、私はチェックアウトするでしょう co.js 。ちなみに非同期待ち関数によく似たcoの関数は約束を返します。正直なところ、現時点ではブラウザの互換性はジェネレータ関数と非同期関数の両方でほぼ同じです。したがって、async待ちの機能が必要な場合は、co.jsなしのAsync関数を使用する必要があります。

ブラウザサポートは、IEを除く現在のすべての主要ブラウザ(Chrome、Safari、およびEdge)のAsync機能(2017年現在)で実際にはかなり良好です。

44
John

http://taskjs.org/ にNice回避策が1つあります。

それはジャバスクリプトに新しいジェネレーターを使用します。そのため、現在ほとんどのブラウザでは実装されていません。私はFirefoxでそれをテストしました、そして私にとっては非同期関数をラップするための素晴らしい方法です。

これはプロジェクトGitHubからのサンプルコードです。

var { Deferred } = task;

spawn(function() {
    out.innerHTML = "reading...\n";
    try {
        var d = yield read("read.html");
        alert(d.responseText.length);
    } catch (e) {
        e.stack.split(/\n/).forEach(function(line) { console.log(line) });
        console.log("");
        out.innerHTML = "error: " + e;
    }

});

function read(url, method) {
    method = method || "GET";
    var xhr = new XMLHttpRequest();
    var deferred = new Deferred();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status >= 400) {
                var e = new Error(xhr.statusText);
                e.status = xhr.status;
                deferred.reject(e);
            } else {
                deferred.resolve({
                    responseText: xhr.responseText
                });
            }
        }
    };
    xhr.open(method, url, true);
    xhr.send();
    return deferred.promise;
}
6

can NodeJSの非同期JavaScriptを sync-rpc と同期させることができます。

それは確かにあなたのUIをフリーズするでしょう、だから私はそれがあなたが取る必要があるショートカットを取ることが可能であるものかどうかに来るとき私はまだ嫌悪者です。 NodeJSが時々それをブロックさせても、JavaScriptでOne And Only Threadを中断することは不可能です。約束が解決するまで、コールバック、イベント、非同期の処理は一切できません。そのため、読者がOPのような避けられない状況(または、私の場合は、コールバック、イベントなどのない賛美されたシェルスクリプトを書いている)がない限り、これをしないでください。

しかし、これがあなたがこれを行うことができる方法です:

./calling-file.js

var createClient = require('sync-rpc');
var mySynchronousCall = createClient(require.resolve('./my-asynchronous-call'), 'init data');

var param1 = 'test data'
var data = mySynchronousCall(param1);
console.log(data); // prints: received "test data" after "init data"

./my-asynchronous-call.js

function init(initData) {
  return function(param1) {
    // Return a promise here and the resulting rpc client will be synchronous
    return Promise.resolve('received "' + param1 + '" after "' + initData + '"');
  };
}
module.exports = init;

制限事項

これらは両方ともsync-rpcがどのように実装されているかの結果であり、それはrequire('child_process').spawnSyncを悪用することによる:

  1. これはブラウザでは機能しません。
  2. あなたの関数の引数でなければなりません。あなたの引数はJSON.stringifyに出入りするので、関数やプロトタイプチェーンのような列挙不可能なプロパティは失われます。
0
meustrus