web-dev-qa-db-ja.com

NodeJSが時間内に完了しなかった場合に約束をタイムアウトする

一定時間後に約束をタイムアウトするにはどうすればよいですか? Qにはプロミスタイムアウトがあることは知っていますが、ネイティブNodeJSプロミスを使用しており、.timeout関数はありません。

不足しているものがあるか、ラップが異なっていますか?

または、以下の実装は、メモリを吸い上げないという意味で優れており、実際に期待どおりに機能していますか?

また、setTimeoutおよびclearTimeoutコードを繰り返すことなく、作成するすべての約束に使用できるように、何らかの方法でグローバルにラップすることができますか?

function run() {
    logger.info('DoNothingController working on process id {0}...'.format(process.pid));

    myPromise(4000)
        .then(function() {
            logger.info('Successful!');
        })
        .catch(function(error) {
            logger.error('Failed! ' + error);
        });
}

function myPromise(ms) {
    return new Promise(function(resolve, reject) {
        var hasValueReturned;
        var promiseTimeout = setTimeout(function() {
            if (!hasValueReturned) {
                reject('Promise timed out after ' + ms + ' ms');
            }
        }, ms);

        // Do something, for example for testing purposes
        setTimeout(function() {
            resolve();
            clearTimeout(promiseTimeout);
        }, ms - 2000);
    });
}

ありがとう!

34
AlexD

ネイティブJavaScriptプロミスにはタイムアウトメカニズムがありません。

あなたの実装に関する質問は、おそらく http://codereview.stackexchange.com に適していますが、いくつかの注意事項があります。

  1. 約束の中で実際に何かをする手段を提供していない、そして

  2. clearTimeoutが一時タイマーをスケジュールするため、setTimeoutコールバック内にsetTimeoutは必要ありません。

  3. 約束が解決/拒否されると、約束は解決/拒否できないため、そのチェックは必要ありません。

おそらくこれらの線に沿って何か:

function myPromise(ms, callback) {
    return new Promise(function(resolve, reject) {
        // Set up the real work
        callback(resolve, reject);

        // Set up the timeout
        setTimeout(function() {
            reject('Promise timed out after ' + ms + ' ms');
        }, ms);
    });
}

このように使用します:

myPromise(2000, function(resolve, reject) {
    // Real work is here
});

(または(mayもう少し複雑にしたい場合は、以下の行の更新を参照してください。)

セマンティクスがわずかに異なる(newなし、newコンストラクターでPromiseを使用する)ことを少し心配するので、調整するかもしれません。

もちろん、もう1つの問題は、ほとんどの場合、新しいプロミスを作成したくないため、上記を使用できないことです。ほとんどの場合、すでに約束があります(以前のthen呼び出しの結果など)。しかし、新しい約束を本当に構築している状況では、上記のようなものを使用できます。

newをサブクラス化することにより、Promiseを処理できます。

class MyPromise extends Promise {
    constructor(ms, callback) {
        // We need to support being called with no milliseconds
        // value, because the various Promise methods (`then` and
        // such) correctly call the subclass constructor when
        // building the new promises they return.
        // This code to do it is ugly, could use some love, but it
        // gives you the idea.
        let haveTimeout = typeof ms === "number" && typeof callback === "function";
        let init = haveTimeout ? callback : ms;
        super((resolve, reject) => {
            init(resolve, reject);
            if (haveTimeout) {
                setTimeout(() => {
                    reject("Timed out");
                }, ms);
            }
        });
    }
}

使用法:

let p = new MyPromise(300, function(resolve, reject) {
    // ...
});
p.then(result => {
})
.catch(error => {
});

ライブの例:

// Uses var instead of let and non-arrow functions to try to be
// compatible with browsers that aren't quite fully ES6 yet, but
// do have promises...
(function() {
    "use strict";
    
    class MyPromise extends Promise {
        constructor(ms, callback) {
            var haveTimeout = typeof ms === "number" && typeof callback === "function";
            var init = haveTimeout ? callback : ms;
            super(function(resolve, reject) {
                init(resolve, reject);
                if (haveTimeout) {
                        setTimeout(function() {
                        reject("Timed out");
                        }, ms);
                }
            });
        }
    }
    
    var p = new MyPromise(100, function(resolve, reject) {
        // We never resolve/reject, so we test the timeout
    });
    p.then(function(result) {
        snippet.log("Resolved: " + result);
    }).catch(function(reject) {
        snippet.log("Rejected: " + reject);
    });
})();
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

コールバックが最初にrejectまたはresolveを呼び出しても、タイマーが期限切れになると、両方ともrejectを呼び出します。それは問題ありません。一度設定すると、約束の確定状態は変更できません。また、仕様は、エラーを発生させないこととして既に確定している約束のresolveまたはrejectへの呼び出しを定義します。 。

しかし、気になる場合は、resolverejectをラップできます。 myPromiseは次のように実行されます。

function myPromise(ms, callback) {
    return new Promise(function(resolve, reject) {
        // Set up the timeout
        let timer = setTimeout(function() {
            reject('Promise timed out after ' + ms + ' ms');
        }, ms);
        let cancelTimer = _ => {
            if (timer) {
                clearTimeout(timer);
                timer = 0;
            }
        };

        // Set up the real work
        callback(
            value => {
                cancelTimer();
                resolve(value);
            },
            error => {
                cancelTimer();
                reject(error);
            }
        );
    });
}

これを約18の異なる方法でスピンできますが、基本的な概念は、受け取ったpromise executorを渡すresolverejectはタイマーをクリアするラッパーであるということです。

しかし、それはあなたが必要としない関数と余分な関数呼び出しを作成します。 仕様は明確です 約束がすでに解決されたときに解決関数が何をするかについて;彼らはかなり早く終了しました。

43
T.J. Crowder

約束のタイムアウトはサポートされていないかもしれませんが、約束を競うことができます。

var race = Promise.race([
  new Promise(function(resolve){
    setTimeout(function() { resolve('I did it'); }, 1000);
  }),
  new Promise(function(resolve, reject){
    setTimeout(function() { reject('Timed out'); }, 800);
  })
]);

race.then(function(data){
  console.log(data);
  }).catch(function(e){
  console.log(e);
  });

汎用Promise.timeout

Promise.timeout = function(timeout, cb){
  return Promise.race([
  new Promise(cb),
  new Promise(function(resolve, reject){
    setTimeout(function() { reject('Timed out'); }, timeout);
  })
]);
}

例:

    Promise.timeout = function(timeout, cb) {
      return Promise.race([
        new Promise(cb),
        new Promise(function(resolve, reject) {
          setTimeout(function() {
            reject('Timed out');
          }, timeout);
        })
      ]);
    }
    
    function delayedHello(cb){
      setTimeout(function(){
        cb('Hello');
        }, 1000);
      }
    
    Promise.timeout(800, delayedHello).then(function(data){
      console.log(data);
      }).catch(function(e){
      console.log(e);
      }); //delayedHello doesn't make it.

    Promise.timeout(1200, delayedHello).then(function(data){
      console.log(data);
      }).catch(function(e){
      console.log(e);
      }); //delayedHello makes it.

実際には2つではなく3つのプロミスを作成しているため、少しコストがかかる場合があります。

関数がそれを構築する代わりに、promiseをセットアップしたい場合があります。このようにして、懸念を分離し、最終的に、xミリ秒で拒否される新たに作成されたプロミスに対してプロミスを競うことに集中します。

Promise.timeout = function(timeout, promise){
  return Promise.race([
  promise,
  new Promise(function(resolve, reject){
    setTimeout(function() { reject('Timed out'); }, timeout);
  })
]);
}

使い方:

var p = new Promise(function(resolve, reject){
    setTimeout(function() { resolve('Hello'); }, 1000);
});

Promise.timeout(800, p); //will be rejected, as the promise takes at least 1 sec.
18
MinusFour

既存のプロミスにタイムアウトを追加するには、次を使用できます。

const withTimeout = (millis, promise) => {
    const timeout = new Promise((resolve, reject) =>
        setTimeout(
            () => reject(`Timed out after ${millis} ms.`),
            millis));
    return Promise.race([
        promise,
        timeout
    ]);
};

それじゃあ、後でね:

await withTimeout(5000, doSomethingAsync());
7
Drew Noakes

これは少し古い質問ですが、約束をタイムアウトする方法を探していたときに、私はこれにつまずきました。
すべての答えは素晴らしいですが、 bluebird Promiseの実装を タイムアウトの処理 の最も簡単な方法として使用していることがわかりました。

var Promise = require('bluebird');
var p = new Promise(function(reject, resolve) { /.../ });
p.timeout(3000) //make the promise timeout after 3000 milliseconds
 .then(function(data) { /handle resolved promise/ })
 .catch(Promise.TimeoutError, function(error) { /handle timeout error/ })
 .catch(function(error) { /handle any other non-timeout errors/ });

ご覧のとおり、これは他の提案されたソリューションよりもはるかに少ない作業です。人々が見つけやすくするためにここに置くと思った:)

ところで、私はブルーバードプロジェクトに関与しているわけではありません。この特定のソリューションは非常にすっきりしています。

7

ここでの答えは有効ですが、車輪の再発明を試みて、NPMで利用可能な数十のパッケージの1つを使用して、自己解決の約束をするべきではありません。

NPMの例

const { TimeoutResolvePromise, TimeoutRejectPromise } = require('nodejs-promise-timeout');
const TIMEOUT_DELAY = 2000;

// This promise will reject after 2 seconds:
let promise1 = new TimeoutRejectPromise(TIMEOUT_DELAY, (resolve, reject) => {
  // Do something useful here, then call resolve() or reject()
});
0
K48