2014-03-19 14 views
29

Ich habe kriskowal's Q library für ein Projekt (Web-Scraper/Mensch-Aktivität-Simulator) verwendet und Versprechungen kennengelernt, sie zurückgegeben und sie auflösen/ablehnen, und die grundlegende asynchrone Kontrolle der Bibliothek Flussmethoden und Fehler-werfende/fangende Mechanismen haben sich als wesentlich erwiesen.Node.js Asynchronous Library Vergleich - Q vs Async

Ich habe jedoch einige Probleme festgestellt. Meine promise.then Anrufe und meine Rückrufe haben die unheimliche Tendenz, Pyramiden zu bilden. Manchmal ist es aus Scoping-Gründen, manchmal ist es eine bestimmte Reihenfolge von Ereignissen zu garantieren. (Ich nehme an, ich könnte einige dieser Probleme durch Refactoring beheben, aber im nächsten Schritt möchte ich die "Callback-Hölle" ganz vermeiden.)

Debugging ist auch sehr frustrierend. Ich verbringe viel Zeit console.log -ing meinen Weg zu der Quelle von Fehlern und Bugs; nachdem ich sie endlich gefunden habe, werde ich anfangen, Fehler dort zu werfen und sie irgendwo anders mit promise.finally zu fangen, aber der Prozess der Lokalisierung der Fehler an erster Stelle ist mühsam.

Auch in meinem Projekt, Bestellung Angelegenheiten. Ich muss so ziemlich alles nacheinander machen. Oftmals erzeuge ich Arrays von Funktionen, die Versprechungen zurückgeben und sie dann aneinander ketten, indem ich Array.prototype.reduce verwende, was ich meiner Meinung nach nicht tun sollte. Hier

ist ein Beispiel für ein meine Methoden, die diese Reduktion Technik verwendet:

removeItem: function (itemId) { 

    var removeRegexp = new RegExp('\\/stock\\.php\\?remove=' + itemId); 

    return this.getPage('/stock.php') 
    .then(function (webpage) { 
    var 
     pageCount = 5, 
     promiseFunctions = [], 
     promiseSequence; 

    // Create an array of promise-yielding functions that can run sequentially. 
    _.times(pageCount, function (i) { 
     var promiseFunction = function() { 
     var 
      promise, 
      path; 

     if (i === 0) { 
      promise = Q(webpage); 
     } else { 
      path = '/stock.php?p=' + i; 
      promise = this.getPage(path); 
     } 

     return promise.then(function (webpage) { 
      var 
      removeMatch = webpage.match(removeRegexp), 
      removePath; 

      if (removeMatch !== null) { 
      removePath = removeitemMatch[0]; 

      return this.getPage(removePath) 
      .delay(1000) 
      // Stop calling subsequent promises. 
      .thenResolve(true); 
      } 

      // Don't stop calling subsequent promises. 
      return false; 

     }.bind(this)); 
     }.bind(this); 

     promiseFunctions.push(promiseFunction); 
    }, this); 

    // Resolve the promises sequentially but stop early if the item is found. 
    promiseSequence = promiseFunctions.reduce(function (soFar, promiseFunction, index) { 
     return soFar.then(function (stop) { 
     if (stop) { 
      return true; 
     } else { 
      return Q.delay(1000).then(promiseFunction); 
     } 
     }); 
    }, Q()); 

    return promiseSequence; 
    }.bind(this)) 
    .fail(function (onRejected) { 
    console.log(onRejected); 
    }); 
}, 

ich andere Methoden, die im Grunde das gleiche tun, aber von viel schlechter Einbuchtung Leiden leiden.

Ich überlege, Refactoring mein Projekt mit coalan's async library. Es scheint Q ähnlich zu sein, aber ich möchte genau wissen, wie sie sich unterscheiden. Der Eindruck, den ich bekomme, ist, dass Async mehr "callback-centric" ist, während Q "Promise-centric" ist.

Frage: Angesichts meiner Probleme und Projektanforderungen, was würde ich gewinnen und/oder verlieren durch die Verwendung von Async über Q? Wie vergleichen die Bibliotheken? (Vor allem in Bezug auf die Ausführung Reihe von Aufgaben der Reihe nach und Debuggen/Fehlerbehandlung?)

+3

Die Anforderung einer sequenziellen Ausführung scheint die meisten Vorteile von async zunichte zu machen. –

+0

Die Leute könnten Sie wahrscheinlich besser beraten, wenn Sie einen besonders unhandlichen Code zeigen, den Sie jetzt verwenden, für den Sie eine bessere Lösung wünschen. Das Diskutieren von Vor- und Nachteilen verschiedener Bibliotheken oder wie man diese Bibliotheken benutzt, ist im Abstrakten viel schwieriger. – jfriend00

+0

@ jfriend00 Ich stimme zu; Ich habe ein Codebeispiel hinzugefügt. – Jackson

Antwort

18

Beide Bibliotheken sind gut. Ich habe entdeckt, dass sie verschiedenen Zwecken dienen und im Tandem verwendet werden können.

Q stellt dem Entwickler Verheißungsobjekte zur Verfügung, bei denen es sich um zukünftige Darstellungen von Werten handelt. Nützlich für Zeitreisen.

Async stellt dem Entwickler asynchrone Versionen von Kontrollstrukturen und Aggregatoperationen zur Verfügung.

Ein Beispiel von einem Versuch einer Linter Implementierung zeigt eine mögliche Einheit unter den Bibliotheken:

function lint(files, callback) { 

    // Function which returns a promise. 
    var getMerged = merger('.jslintrc'), 

     // Result objects to invoke callback with. 
     results = []; 

    async.each(files, function (file, callback) { 
     fs.exists(file, function (exists) { 

      // Future representation of the file's contents. 
      var contentsPromise, 

       // Future representation of JSLINT options from .jslintrc files. 
       optionPromise; 

      if (!exists) { 
       callback(); 
       return; 
      } 

      contentsPromise = q.nfcall(fs.readFile, file, 'utf8'); 
      optionPromise = getMerged(path.dirname(file)); 

      // Parallelize IO operations. 
      q.all([contentsPromise, optionPromise]) 
       .spread(function (contents, option) { 
        var success = JSLINT(contents, option), 
         errors, 
         fileResults; 
        if (!success) { 
         errors = JSLINT.data().errors; 
         fileResults = errors.reduce(function (soFar, error) { 
          if (error === null) { 
           return soFar; 
          } 
          return soFar.concat({ 
           file: file, 
           error: error 
          }); 
         }, []); 
         results = results.concat(fileResults); 
        } 
        process.nextTick(callback); 
       }) 
       .catch(function (error) { 
        process.nextTick(function() { 
         callback(error); 
        }); 
       }) 
       .done(); 
     }); 
    }, function (error) { 
     results = results.sort(function (a, b) { 
      return a.file.charCodeAt(0) - b.file.charCodeAt(0); 
     }); 
     callback(error, results); 
    }); 
} 

ich für jede Datei möglicherweise blockierenden, etwas tun will. So ist async.each die offensichtliche Wahl. Ich kann bezogene Operationen per-Iteration mit parallelisieren und meine Optionswerte wiederverwenden, wenn sie für 2 oder mehr Dateien gelten.

Hier beeinflussen Async und Q jeweils den Steuerungsfluss des Programms, und Q stellt Werte dar, die irgendwann in der Zukunft in Dateiinhalte aufgelöst werden. Die Bibliotheken arbeiten gut zusammen. Man muss sich nicht "über den anderen entscheiden".

0

Dies ist zwar immer noch keine wirkliche Antwort auf meine Frage (Q vs async), in Bezug auf meine Problem, ich habe gefunden Selen/WebDriverJs, um eine praktikable Lösung zu sein.

WebDriver verwendet eine Warteschlange, um Versprechungen der Reihe nach auszuführen, was immens bei der Kontrolle der Einrückung hilft. Seine Versprechen sind auch mit Qs kompatibel.

Das Erstellen einer Abfolge von Versprechen ist kein Problem mehr. Eine einfache For-Schleife wird ausreichen.

Um früh in einer Sequenz zu stoppen, tun Sie dies nicht. Verwenden Sie statt einer Sequenz ein asynchrones Design und eine Verzweigung.

+4

Lassen Sie sich nicht von jemandem beeinflussen, der mich enttäuscht hat. Wenn Sie in JS einen "Web Scraper/Human-Activity-Simulator" schreiben, verschwenden Sie nicht Ihre Zeit damit, durch ein Meer überlappender "aufgeschobener" Variablen zu schwimmen. Verwenden Sie WebDriver. – Jackson

+1

Ich muss sagen, dass ich nie daran gedacht habe, Selen zum Schaben zu verwenden, ich benutze es für Tests als Simulator für menschliche Aktivität, das ist aber interessant. Auch Webdriver Versprechen sind nicht kompatibel mit Qs, sie bewerten anders und können Ihnen falsche Positive geben. Wenn Sie benutzerdefinierte Versprechen erstellen müssen, verwenden Sie webdriver.promise(). – RadleyMith

2

Callback-Pyramiden in Ihrem Code können mithilfe von Promenzusammensetzung und lexikalischem JavaScript-Scoping vereinfacht werden.

removeItem: function (itemId) { 

    var removeRegexp = new RegExp('\\/stock\\.php\\?remove=' + itemId); 
    var found = false 
    var promise = getPage('/sock.php') 

    _.times(5, (i) => { 
    promise = promise.then((webpage) => { 
     if (found) return true 
     var removeMatch = webpage.match(removeRegexp) 
     var found = removeMath !== null 
     var nextPage = found ? removeMatch[0] : '/stock.php?p='+i+1 
     return Q.delay(1000).then(() => this.getPage(nextPage)) 
    }) 
    }) 

    return promise.fail(console.log.bind(console)) 

}, 

IMHO async sollte nicht in neuem JavaScript-Code verwendet werden. Versprechen sind kompostierbarer und erlauben viel mehr intuitiven Code.

Der Hauptgrund, warum Knoten keine Versprechungen genutzt haben war wegen Leistungsprobleme, die weitgehend von Bibliotheken wie Drossel und Q. sehr gut behandelt wurden

Wie async/erwarten Syntax mehr Mainstream wird, werden verspricht den Weg ebnen für Code, der mit synchronem Code sehr ähnlich aussieht.