loop - node.js setinterval




Während Schleife mit Versprechen (8)

Da ich Stuart Ks Antwort nicht kommentieren kann, werde ich hier ein wenig hinzufügen. Basierend auf der Antwort von Stuart K. können Sie es auf ein überraschend einfaches Konzept reduzieren : Verwenden Sie ein unerfülltes Versprechen . Was er hat, ist im Wesentlichen:

  1. Erstellen Sie eine neue Instanz eines verzögerten Versprechens
  2. Definieren Sie Ihre Funktion, die Sie in einer Schleife aufrufen möchten
  3. Innerhalb dieser Funktion:
    1. Überprüfen Sie, ob Sie fertig sind. und wenn Sie gelöst sind, lösen Sie das in # 1 erstellte Versprechen und geben es zurück.
    2. Wenn Sie nicht fertig sind, sagen Sie Q, dass das vorhandene Versprechen verwendet werden soll, und führen Sie die Funktion "unfullfilled" aus, die die Funktion "recursive" ist, oder führen Sie einen Fehler durch, wenn sie verfällt. Q.when (Versprechen, yourFunction, failFunction)
  4. Nachdem Sie Ihre Funktion definiert haben, verwenden Sie Q, um die Funktion zum ersten Mal mit Q.nextTick (yourFunction) auszulösen
  5. Schließlich geben Sie Ihr neues Versprechen an den Anrufer zurück (wodurch die ganze Sache ausgelöst wird).

Stuart's Antwort ist für eine allgemeinere Lösung, aber die Grundlagen sind fantastisch (sobald man merkt, wie es funktioniert).

Was wäre die idiomatische Art, so etwas wie eine Weile mit Versprechungen zu machen. Damit:

etwas tun, wenn der Zustand noch besteht, wiederhole es, tu etwas anderes.

dosomething.then(possilblydomoresomethings).then(finish)

Ich habe es so gemacht, dass ich mich gefragt habe, ob es bessere / mehr idomische Wege gibt?

var q = require('q');

var index = 1;

var useless =  function(){
        var currentIndex = index;
        console.log(currentIndex)
        var deferred = q.defer();
        setTimeout(function(){
            if(currentIndex > 10)
                deferred.resolve(false);
            else deferred.resolve(true);
            },500);
        return deferred.promise;
    }

var control = function(cont){
        var deferred = q.defer();
        if(cont){
                index = index + 1;
                useless().then(control).then(function(){
                        deferred.resolve();
                    });
            }
         else deferred.resolve();
        return deferred.promise;
    }

var chain = useless().then(control).then(function(){console.log('done')});

Ausgabe: 1 2 3 4 5 6 7 8 9 10 11 fertig


Dies ist der einfachste Weg, den ich gefunden habe, um das Grundmuster auszudrücken: Sie definieren eine Funktion, die das Versprechen aufruft, sein Ergebnis überprüft und sich dann entweder selbst wieder aufruft oder sich selbst beendet.

const doSomething = value =>
  new Promise(resolve => 
    setTimeout(() => resolve(value >= 5 ? 'ok': 'no'), 1000))

const loop = value =>
  doSomething(value).then(result => {
    console.log(value)
    if (result === 'ok') {
      console.log('yay')      
    } else {
      return loop(value + 1)
    }
  })

loop(1).then(() => console.log('all done!'))

Sehen Sie es in Aktion auf JSBin

Wenn Sie eine Zusage verwenden, die auflöst oder ablehnt, würden Sie then und catch definieren catch anstatt eine if-Klausel zu verwenden.

Wenn Sie eine Reihe von Versprechungen hatten, würden Sie einfach die loop wechseln oder jedes Mal die nächste setzen.

EDIT: Hier ist eine Version, die async/await , weil es 2018 ist:

const loop = async value => {
  let result = null
  while (result != 'ok') {
    console.log(value)
    result = await doSomething(value)
    value = value + 1
  }
  console.log('yay')
}

Sehen Sie es in Aktion auf CodePen

Wie Sie sehen können, verwendet es eine normale While-Schleife und keine Rekursion.


Dieses Muster wird jetzt einfacher mit q-flow aufgerufen. Ein Beispiel für das obige Problem:

var q = require('q');
require('q-flow');
var index = 1;
q.until(function() {
  return q.delay(500).then(function() {
    console.log(index++);
    return index > 10;
  });
}).done(function() {
  return console.log('done');
});

Hier ist eine Erweiterung des Promise Prototyps, um das Verhalten einer for Schleife nachzuahmen. Es unterstützt Versprechen oder unmittelbare Werte für die Abschnitte Initialisierung, Bedingung, Schleifenkörper und Inkrement. Es hat auch volle Unterstützung für Ausnahmen und es hat keine Speicherlecks. Ein Beispiel wird unten gegeben, wie man es benutzt.

var Promise = require('promise');


// Promise.loop([properties: object]): Promise()
//
//  Execute a loop based on promises. Object 'properties' is an optional
//  argument with the following fields:
//
//  initialization: function(): Promise() | any, optional
//
//      Function executed as part of the initialization of the loop. If
//      it returns a promise, the loop will not begin to execute until
//      it is resolved.
//
//      Any exception occurring in this function will finish the loop
//      with a rejected promise. Similarly, if this function returns a
//      promise, and this promise is reject, the loop finishes right
//      away with a rejected promise.
//
//  condition: function(): Promise(result: bool) | bool, optional
//
//      Condition evaluated in the beginning of each iteration of the
//      loop. The function should return a boolean value, or a promise
//      object that resolves with a boolean data value.
//
//      Any exception occurring during the evaluation of the condition
//      will finish the loop with a rejected promise. Similarly, it this
//      function returns a promise, and this promise is rejected, the
//      loop finishes right away with a rejected promise.
//
//      If no condition function is provided, an infinite loop is
//      executed.
//
//  body: function(): Promise() | any, optional
//
//      Function acting as the body of the loop. If it returns a
//      promise, the loop will not proceed until this promise is
//      resolved.
//
//      Any exception occurring in this function will finish the loop
//      with a rejected promise. Similarly, if this function returns a
//      promise, and this promise is reject, the loop finishes right
//      away with a rejected promise.
//
//  increment: function(): Promise() | any, optional
//
//      Function executed at the end of each iteration of the loop. If
//      it returns a promise, the condition of the loop will not be
//      evaluated again until this promise is resolved.
//
//      Any exception occurring in this function will finish the loop
//      with a rejected promise. Similarly, if this function returns a
//      promise, and this promise is reject, the loop finishes right
//      away with a rejected promise.
//
Promise.loop = function(properties)
{
    // Default values
    properties = properties || {};
    properties.initialization = properties.initialization || function() { };
    properties.condition = properties.condition || function() { return true; };
    properties.body = properties.body || function() { };
    properties.increment = properties.increment || function() { };

    // Start
    return new Promise(function(resolve, reject)
    {
        var runInitialization = function()
        {
            Promise.resolve().then(function()
            {
                return properties.initialization();
            })
            .then(function()
            {
                process.nextTick(runCondition);
            })
            .catch(function(error)
            {
                reject(error);
            });
        }

        var runCondition = function()
        {
            Promise.resolve().then(function()
            {
                return properties.condition();
            })
            .then(function(result)
            {
                if (result)
                    process.nextTick(runBody);
                else
                    resolve();
            })
            .catch(function(error)
            {
                reject(error);
            });
        }

        var runBody = function()
        {
            Promise.resolve().then(function()
            {
                return properties.body();
            })
            .then(function()
            {
                process.nextTick(runIncrement);
            })
            .catch(function(error)
            {
                reject(error);
            });
        }

        var runIncrement = function()
        {
            Promise.resolve().then(function()
            {
                return properties.increment();
            })
            .then(function()
            {
                process.nextTick(runCondition);
            })
            .catch(function(error)
            {
                reject(error);
            });
        }

        // Start running initialization
        process.nextTick(runInitialization);
    });
}


// Promise.delay(time: double): Promise()
//
//  Returns a promise that resolves after the given delay in seconds.
//
Promise.delay = function(time)
{
    return new Promise(function(resolve)
    {
        setTimeout(resolve, time * 1000);
    });
}


// Example
var i;
Promise.loop({
    initialization: function()
    {
        i = 2;
    },
    condition: function()
    {
        return i < 6;
    },
    body: function()
    {
        // Print "i"
        console.log(i);

        // Exception when 5 is reached
        if (i == 5)
            throw Error('Value of "i" reached 5');

        // Wait 1 second
        return Promise.delay(1);
    },
    increment: function()
    {
        i++;
    }
})
.then(function()
{
    console.log('LOOP FINISHED');
})
.catch(function(error)
{
    console.log('EXPECTED ERROR:', error.message);
});

Ich benutze das jetzt:

function each(arr, work) {
  function loop(arr, i) {
    return new Promise(function(resolve, reject) {
      if (i >= arr.length) {resolve();}
      else try {
        Promise.resolve(work(arr[i], i)).then(function() { 
          resolve(loop(arr, i+1))
        }).catch(reject);
      } catch(e) {reject(e);}
    });
  }
  return loop(arr, 0);
}

Dies akzeptiert ein Array arr und eine Funktion und gibt eine Promise . Die angegebene Funktion wird für jedes Element im Array einmal aufgerufen und erhält das aktuelle Element und seinen Index im Array. Es kann sync oder async sein, in diesem Fall muss eine Promise zurückgegeben werden.

Du kannst es so benutzen:

var items = ['Hello', 'cool', 'world'];
each(items, function(item, idx) {
    // this could simply be sync, but can also be async
    // in which case it must return a Promise
    return new Promise(function(resolve){
        // use setTimeout to make this async
        setTimeout(function(){
            console.info(item, idx);
            resolve();
        }, 1000);
    });
})
.then(function(){
    console.info('DONE');
})
.catch(function(error){
    console.error('Failed', error);
})

Jedes Element im Array wird der Reihe nach behandelt. Sobald alle behandelt sind, wird der Code für .then() ausgeführt, oder, wenn ein Fehler aufgetreten ist, der Code, der an .catch() . Innerhalb der work können Sie einen Error (bei synchronen Funktionen) auslösen oder die Promise (bei asynchronen Funktionen) reject , um die Schleife abzubrechen.

function each(arr, work) {
  function loop(arr, i) {
    return new Promise(function(resolve, reject) {
      if (i >= arr.length) {resolve();}
      else try {
        Promise.resolve(work(arr[i], i)).then(function() { 
          resolve(loop(arr, i+1))
        }).catch(reject);
      } catch(e) {reject(e);}
    });
  }
  return loop(arr, 0);
}

var items = ['Hello', 'cool', 'world'];
each(items, function(item, idx) {
  // this could simply be sync, but can also be async
  // in which case it must return a Promise
  return new Promise(function(resolve){
    // use setTimeout to make this async
    setTimeout(function(){
      console.info(item, idx);
      resolve();
    }, 1000);
  });
})
.then(function(){
  console.info('DONE');
})
.catch(function(error){
  console.error('Failed', error);
})


Ich dachte, ich könnte genauso gut meinen Hut in den Ring werfen, mit ES6 Promises ...

function until_success(executor){
    var before_retry = undefined;
    var outer_executor = function(succeed, reject){
        var rejection_handler = function(err){
            if(before_retry){
                try {
                    var pre_retry_result = before_retry(err);
                    if(pre_retry_result)
                        return succeed(pre_retry_result);
                } catch (pre_retry_error){
                    return reject(pre_retry_error);
                }
            }
            return new Promise(executor).then(succeed, rejection_handler);                
        }
        return new Promise(executor).then(succeed, rejection_handler);
    }

    var outer_promise = new Promise(outer_executor);
    outer_promise.before_retry = function(func){
        before_retry = func;
        return outer_promise;
    }
    return outer_promise;
}

Das executor Argument ist das gleiche wie das an einen Promise Konstruktor übergebene, wird aber wiederholt aufgerufen, bis es den Erfolgsrückruf auslöst. Die before_retry Funktion ermöglicht eine benutzerdefinierte Fehlerbehandlung bei fehlgeschlagenen Versuchen. Wenn es einen truthy Wert zurückgibt, wird es als eine Form des Erfolges betrachtet, und die "Schleife" wird enden, mit dem truthy als das Ergebnis. Wenn keine before_retry Funktion registriert ist oder einen falschen Wert zurückgibt, wird die Schleife für eine weitere Iteration ausgeführt. Die dritte Option besteht darin, dass die Funktion " before_retry selbst einen Fehler before_retry . Wenn dies passiert, wird die "Schleife" enden und diesen Fehler als einen Fehler weiterleiten.

Hier ist ein Beispiel:

var counter = 0;
function task(succ, reject){
    setTimeout(function(){
        if(++counter < 5)
            reject(counter + " is too small!!");
        else
            succ(counter + " is just right");
    }, 500); // simulated async task
}

until_success(task)
        .before_retry(function(err){
            console.log("failed attempt: " + err);
            // Option 0: return falsey value and move on to next attempt
            // return

            // Option 1: uncomment to get early success..
            //if(err === "3 is too small!!") 
            //    return "3 is sort of ok"; 

            // Option 2: uncomment to get complete failure..
            //if(err === "3 is too small!!") 
            //    throw "3rd time, very unlucky"; 
  }).then(function(val){
       console.log("finally, success: " + val);
  }).catch(function(err){
       console.log("it didn't end well: " + err);
  })

Ausgabe für Option 0:

failed attempt: 1 is too small!!
failed attempt: 2 is too small!!
failed attempt: 3 is too small!!
failed attempt: 4 is too small!!
finally, success: 5 is just right

Ausgabe für Option 1:

failed attempt: 1 is too small!!
failed attempt: 2 is too small!!
failed attempt: 3 is too small!!
finally, success: 3 is sort of ok

Ausgabe für Option 2:

failed attempt: 1 is too small!!
failed attempt: 2 is too small!!
failed attempt: 3 is too small!!
it didn't end well: 3rd time, very unlucky

Ich würde ein Objekt verwenden, um den Wert zu umbrechen. Auf diese Weise können Sie eine " done -Eigenschaft erstellen, damit die Schleife weiß, dass Sie fertig sind.

// fn should return an object like
// {
//   done: false,
//   value: foo
// }
function loop(promise, fn) {
  return promise.then(fn).then(function (wrapper) {
    return !wrapper.done ? loop(Q(wrapper.value), fn) : wrapper.value;
  });
}

loop(Q.resolve(1), function (i) {
  console.log(i);
  return {
    done: i > 10,
    value: i++
  };
}).done(function () {
  console.log('done');
});

Mit dem ES6-Versprechen kam ich auf diese Idee. Es verkettet die Versprechen und gibt ein Versprechen ab. Es ist nicht technisch eine while-Schleife, sondern zeigt, wie synchron über Versprechen zu iterieren ist.

function chain_promises(list, fun) {
    return list.reduce(
        function (promise, element) {
            return promise.then(function () {
                // I only needed to kick off some side-effects. If you need to get
                // a list back, you would append to it here. Or maybe use
                // Array.map instead of Array.reduce.
                fun(element);
            });
    	},
        // An initial promise just starts things off.
        Promise.resolve(true)
    );
}

// To test it...

function test_function (element) {
    return new Promise(function (pass, _fail) {
        console.log('Processing ' + element);
        pass(true);
    });
}

chain_promises([1, 2, 3, 4, 5], test_function).then(function () {
    console.log('Done.');
});

Hier ist meine Geige.





q