1. ホーム
  2. javascript

[解決済み] Node.jsやJavascriptで非同期関数呼び出しを同期関数にラップする方法とは?

2022-06-02 02:30:13

質問

ある関数を公開するライブラリをメンテナンスしているとします。 getData . ユーザーは実際のデータを取得するためにそれを呼び出します。

var output = getData();

裏側ではデータはファイルに保存されているので、実装した getData を使用して、Node.js 組み込みの fs.readFileSync . これは明らかに getDatafs.readFileSync は同期関数です。ある日、基礎となるデータソースをMongoDBのような非同期でしかアクセスできないリポジトリに変更するように言われました。また、ユーザーを怒らせないようにするように言われました。 getData APIは、単なるプロミスを返したり、コールバックパラメータを要求するように変更することはできません。どのようにして両方の要件を満たすのでしょうか?

コールバック/プロミスを使った非同期関数は、JavasSriptやNode.jsのDNAです。どんな非自明な JS アプリでも、おそらくこのコーディング スタイルが浸透しています。しかし、このやり方は、いわゆる「コールバックのピラミッド」と呼ばれる破滅的な状況に陥りやすい。さらに悪いことに、コールチェーン内の呼び出し側のコードが非同期関数の結果に依存している場合、それらのコードもコールバック関数でラップしなければならず、呼び出し側にコーディングスタイルの制約を課してしまいます。私は時々、大規模なグローバルリファクタリングを避けるために、非同期関数(多くの場合サードパーティライブラリで提供される)を同期関数にカプセル化する必要性を感じることがあります。このテーマで解決策を探すと、大抵は ノード ファイバー またはそれに由来する npm パッケージに行き着きます。しかし、Fibers は私が直面している問題を解決することができません。Fibers の作者が提供した例でも、その欠陥が示されています。

...
Fiber(function() {
    console.log('wait... ' + new Date);
    sleep(1000);
    console.log('ok... ' + new Date);
}).run();
console.log('back in main');

実際の出力です。

wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
back in main
ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)

関数Fiberが本当に非同期関数sleepを同期に変えるのであれば、出力はこうなるはずです。

wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)
back in main

もう一つ簡単な例を JSFiddle を作成し、期待される出力を得るためのコードを探しています。私はNode.jsでのみ動作するソリューションを受け入れるので、JSFiddleで動作しないにもかかわらず、任意のnpmパッケージを必要とする自由があります。

どのように解決するのですか?

デシンク は非同期関数を同期に変換します。JavaScriptレイヤーでNode.jsのイベントループを呼び出すことによって、ブロッキング機構で実装されています。その結果、deasyncはスレッド全体をブロックすることなく、後続のコードの実行をブロックするだけで、ビジーウェイトを発生させることもありません。このモジュールで、jsFiddleの課題に対する答えがあります。

function AnticipatedSyncFunction(){
  var ret;
  setTimeout(function(){
      ret = "hello";
  },3000);
  while(ret === undefined) {
    require('deasync').runLoopOnce();
  }
  return ret;    
}


var output = AnticipatedSyncFunction();
//expected: output=hello (after waiting for 3 sec)
console.log("output="+output);
//actual: output=hello (after waiting for 3 sec)

(免責事項: 私は共著者である deasync . モジュールはこの質問を投稿した後に作成されましたが、実行可能な提案は見つかりませんでした)。