2016-09-19 6 views
0

Der zweite Teil des Versprechens unten (innerhalb der then) wird nie ausgeführt. Wenn ich die Datenbankabfrage ohne die Promise (in einem Knoten-Skript, das ich ausführen, die Daten aber die Konsole nie die Eingabeaufforderung zurückgibt - hängt die Konsole einfach und ich muss einen Interrupt manuell senden. Daher, wenn ich es setze Ich glaube, das Promise weiß nicht, dass die Datenbankabfrage abgeschlossen ist, obwohl es anscheinend alle Daten zurückgegeben hat, daher läuft der zweite Teil des Versprechens nicht (denke ich) wie schreibe ich die Abfrage-Datenbank, so dass sie nicht hängen und das Versprechen zur Fertigstellung betreiben?Versprechen wird nicht zurückgegeben

const sqlite = require('/usr/local/lib/node_modules/sqlite3'); 
const express = require('/usr/local/lib/node_modules/express') 
const promise = require('/usr/local/lib/node_modules/promise') 

app.get('/', (request, res) => { 

    var res = []; 

    function getData() { 
    return new Promise(function(resolve, reject) { 
     db.each('SELECT column_a, column_b FROM trips group by column_a', (e, rows) => { 

     var d = { 
      a: rows['column_a'], 
      b: rows['column_b'] 
     } 


     res.push(d) 
     }); 
    }); 

    } 
    getData().then(function(data) { 
    console.log("never run....", res, data) //never run 
    }); 

}) 
+1

Ich würde empfehlen, ein Versprechen-basierte Bibliothek wie 'Knoten-sqlite' oder' q-sqlite3 mit '(ist ein Versprechen Wrapper' sqlite3'), anstatt alle Funktionsaufrufe manuell zu wickeln. Es würde eine große Menge an Tipparbeit und Wartung sparen. – tcooc

+0

können Sie eine Verknüpfung zu der node-sqlite-Bibliothek herstellen, auf die Sie sich beziehen? '' 'Npm install -g node-sqlite''' sagt' Registry hat 404 für GET unter http: // registry.npmjs.org/node-sqlite' zurückgegeben – Leahcim

+0

https://www.npmjs.com/package/sqlite Betrachtet man es wieder, ist es nur ein Versprechen Wrapper um sqlite3 (ähnlich wie q-sqlite3, aber native Versprechen verwendet). – tcooc

Antwort

7

Sie benötigen ein Versprechen zu lösen, indem eine der Funktionen nennt es in dem Rückruf durch den Konstruktor bietet

const promise = new Promise((resolve, reject) => { 
    // you must call resolve() or reject() here 
    // otherwise the promise never resolves 
}); 

Sonst bleibt es immer in Ausstehend Zustand und rufen Sie nie die Rückrufe (Callbacks) an, die Sie in then übergeben.

Zusagen können Sie auch mit Werten auflösen, so dass es in der Regel nicht notwendig ist, globale Variablen zu verwalten, Sie können einfach Ergebnisse übergeben.

Schließlich wird der Rückruf in db.eacheinmal für jede Zeile aufgerufen werden, so dass Sie damit umgehen müssten durch das Versprechen der Lösung , nachdem alle Zeilen erhalten worden

Hier ist, wie Sie Ihren Code schreiben konnte :

function getData() { 
    const data = []; 
    return new Promise((resolve, reject) => { 
    db.each('SELECT column_a, column_b FROM trips group by column_a', (e, row) => { 
     if (e) { 
     // error reading a row, reject the Promise immediately 
     // optionally you could accumulate errors here in a similar manner to rows 
     reject(e); 
     return; 
     } 

     // success reading a row, store the row result 
     data.push({ 
     a: row['column_a'], 
     b: row['column_b'] 
     }); 

    }, (e, rowCount) => { // the complete handler called when the operation is done, see docs: https://github.com/mapbox/node-sqlite3/wiki/API#databaseeachsql-param--callback-complete 

     if (e) { 
     // operation finished, there was an error 
     reject(e); 
     return; 
     } 

     // operation succeeded, resolve with rows 
     resolve(data); 
    }); 
    }); 
} 

app.get('/', (request, res) => { 

    getData().then((data) => { 
    // here `data` is an array of row objects 
    }, (e) => { 
    console.error(`Database error: ${e}`); 
    }); 
}); 

Side Hinweis

nicht sicher, warum Sie deklarieren den Parameter res als [], aber Sie müssen var res = [] nicht tun. Da Sie bereits res haben, können Sie einfach res = [] zu Punkt res zu einem neuen Array sagen. Natürlich überschreibt das das Antwortobjekt, also nehme ich an, dass Sie es nur für die Zwecke dieses Beispiels tun. Wenn nicht, sollten Sie wahrscheinlich eine neue Variable erstellen.

+0

Es gibt mehr zu einer vollständigen Korrektur, als Sie anzeigen, weil Sie nicht 'resolve()' innerhalb der 'db.each()' aufrufen möchten, da dies mehrmals aufgerufen wird und nur das erste Mal tatsächlich etwas bewirkt. – jfriend00

+0

Sie sind absolut richtig. Ich habe nicht überlegt, in was für ein Versprechen man sich eingewickelt hat, lassen Sie mich das beheben. – nem035

+0

@ jfriend00 sollte jetzt behoben werden – nem035

1

Sie haben ein Versprechen erklärt, das bedeutet, dass Sie für den Aufruf eines von resolve verantwortlich oder rejecteinmal und nur einmal.

Hier ist ein gereinigtes Beispiel:

app.get('/', (request, res) => { 
    var res = [ ]; 

    new Promise((resolve, reject) => { 
    db.each('SELECT column_a, column_b FROM trips group by column_a', (e, row) => { 
     if (e) { 
     reject(e); 

     return; 
     } 

     res.push({ 
      a: row['column_a'], 
      b: row['column_b'] 
     }); 
    }, (err) => { 
     if (err) { 
     return reject(err); 
     } 

     resolve(res); 
    }); 
    }).then((data) => { 
    console.log("Running ", res, data)//never run 
    } 
}); 

Wenn Ihre Datenbank-Layer unterstützt Versprechungen, die in die Regel diese Art von Code, da man einfach viel weniger chaotisch machen, kann Kette, die da drin.

Edit: Da die Sqlite3 API ist bizarr Nicht-Standard und die each function hat zwei Rückrufe Sie jeden row mit den ersten, dann den Abschluss-Handler mit dem zweiten behandeln müssen.

Wenn Sie eine API wie diese entwerfen, machen Sie es falsch. Nicht.

+2

Aufruf von resolve() oder reject() mehrmals wird ignoriert, so dass die Verantwortung, die du in der ersten Zeile erwähnst, keine Rolle spielt :) – nem035

+0

@ nem035 Mehrfaches Anrufen kann ignoriert werden, aber das bedeutet nicht, dass es getan werden sollte. Ebenso ist es ein ernstes Problem, es nullmal zu nennen. Es gibt einen Unterschied zwischen dem, was Sie theoretisch tun dürfen und dem, was Sie tun sollten. – tadman

+1

Sie haben Recht, kann damit nicht argumentieren. Ich wollte nur sagen, dass wir die Rückrufe nicht wirklich einmal anrufen müssen, aber ja, wir sollten es auf jeden Fall tun. – nem035

1

Mehrere Punkte:

  • resolve/reject muss aufgerufen werden, da sonst ein new Promise() wird für immer bleiben "offen".
  • immer Promisify auf der niedrigsten möglichen Ebene, dh Promisify db.each() nicht getData(). Dies gibt Ihnen ein testbares, wiederverwendbares Dienstprogramm und einen verständlicheren Anwendungscode.
  • db.each() ist eine Herausforderung zu promisifizieren, weil es zwei mögliche Fehlerquellen hat; eine in ihrem Iterationsrückruf und eine in ihrem vollständigen Rückruf.
  • Die sqlite3-Dokumentation gibt nicht an, was passiert, wenn ein Iterationsfehler auftritt, aber vermutlich wird die Iteration fortgesetzt, sonst erscheint der Fehler einfach als Abschlussfehler?

Hier ein paar Möglichkeiten promisify:

1. Erste Iterationsfehler oder Beendigung Fehlerursachen Ablehnung versprechen - Iteration Fehler werden in den Anwendungscode nicht ausgesetzt.

// Promisification 
db.eachAsync = function(sql, iterationCallback) { 
    return new Promise(function(resolve, reject) { 
     db.each(sql, (iterationError, row) => { 
      if(iterationError) { 
       reject(iterationError); 
      } else { 
       iterationCallback(row); 
      } 
     }, (completionError, n) => { 
      if(completionError) { 
       reject(completionError); 
      } else { 
       resolve(n); // the number of retrieved rows. 
      } 
     }); 
    }); 
}; 

// Application 
app.get('/', (request, response) => { 
    function getData() { 
     var res = []; 
     return db.eachAsync('SELECT column_a, column_b FROM trips group by column_a', (row) => { 
      res.push({ 
       a: row['column_a'], 
       b: row['column_b'] 
      }); 
     }).then(n => res); 
    } 
    getData().then(results => { 
     console.log(results); 
    }).catch(error => { 
     console.log(error); 
    }); 
}); 

2. Nur ein Abschluss Fehlerursachen versprechen Ablehnung - Iteration Fehler Anwendungscode ausgesetzt sind

// Promisification 
db.eachAsync = function(sql, iterationCallback) { 
    return new Promise(function(resolve, reject) { 
     db.each(sql, iterationCallback, (completionError, n) => { 
      if(completionError) { 
       reject(completionError); 
      } else { 
       resolve(n); // the number of retrieved rows. 
      } 
     }); 
    }); 
}; 

// Application 
app.get('/', (request, response) => { 
    function getData() { 
     var res = []; 
     return db.eachAsync('SELECT column_a, column_b FROM trips group by column_a', (iterationError, row) => { 
      // You can choose what to do on iterationError. 
      // Here, nulls are injected in place of values from the db, 
      // but you might choose not to push anything. 
      res.push({ 
       a: iterationError ? null : row['column_a'], 
       b: iterationError ? null : row['column_b'] 
      }); 
     }).then(n => res); 
    } 
    getData().then(results => { 
     console.log(results); 
    }).catch(error => { 
     console.log(error); 
    }); 
}); 

(2) ist der bessere Ansatz, da Fehler Iteration Aussetzen bietet Ihnen mehr Flexibilität . Zum Beispiel könnten Sie wählen, mit (2) und emulieren (1) in Ihrer Anwendung promisify:

// Application 
app.get('/', (request, response) => { 
    function getData() { 
     var res = []; 
     var e = null; 
     return db.eachAsync('SELECT column_a, column_b FROM trips group by column_a', (iterationError, row) => { 
      if(iterationError && !e) { 
       // remember the first iteration error 
       e = iterationError; 
      } else { 
       // push only on success 
       res.push({ 
        a: row['column_a'], 
        b: row['column_b'] 
       }); 
      } 
     }).then(n => { 
      if(e) { 
       throw e; 
      } else { 
       return res; 
      } 
     }); 
    } 
    getData().then(results => { 
     console.log(results); 
    }).catch(error => { 
     console.log(error); 
    }); 
}); 

Mit (1), indem Sie auf dem ersten Iterationsfehler Ablehnung anstatt Iteration Fehler ausgesetzt wird, die gleiche Flexibilität ist Nicht verfügbar. (1) konnte nicht vollständig emulieren (2).

Glücklicherweise ist der bevorzugte Ansatz (2) ist das gleiche wie mit Bluebird .promisify() Verfahren erhalten würde:

Promise.promisify(db.each); 
Verwandte Themen