2016-04-09 7 views
-1

machen Ich möchte durch eine Reihe von Studenten iterieren und machen HTTP-Aufruf für jeden von ihnen und analysieren die Antwort und einfügen in mongodb, so würde ich dies für tun möchten jeder Schüler nacheinander bis alle Daten eingeschoben sind, fahre mit dem nächsten fort, so dass es besser für CPU und RAM wäre Speicher ...wie synchrone HTTP-Aufrufe mit Versprechungen in node.js

Bisher mache ich das, aber das ist aus irgendeinem Grund nicht was ich wollte ...

var startDate = new Date("February 20, 2016 00:00:00"); //Start from February 
var from = new Date(startDate).getTime()/1000; 
startDate.setDate(startDate.getDate() + 30); 
var to = new Date(startDate).getTime()/1000; 

iterateThruAllStudents(from, to); 

function iterateThruAllStudents(from, to) { 
    Student.find({status: 'student'}) 
     .populate('user') 
     .exec(function (err, students) { 
      if (err) { 
       throw err; 
      } 

      async.eachSeries(students, function iteratee(student, callback) { 
       if (student.worksnap.user != null) { 
        var worksnapOptions = { 
         hostname: 'worksnaps.com', 
         path: '/api/projects/' + project_id + '/time_entries.xml?user_ids=' + student.worksnap.user.user_id + '&from_timestamp=' + from + '&to_timestamp=' + to, 
         headers: { 
          'Authorization': 'Basic xxx=' 
         }, 
         method: 'GET' 
        }; 

        promisedRequest(worksnapOptions) 
         .then(function (response) { //callback invoked on deferred.resolve 
          parser.parseString(response, function (err, results) { 
           var json_string = JSON.stringify(results.time_entries); 
           var timeEntries = JSON.parse(json_string); 
           _.forEach(timeEntries, function (timeEntry) { 
            _.forEach(timeEntry, function (item) { 
             saveTimeEntry(item); 
            }); 
           }); 
           callback(null); 
          }); 
         }, function (newsError) { //callback invoked on deferred.reject 
          console.log(newsError); 
         }); 
       } 
      }); 
     }); 
} 

function saveTimeEntry(item) { 
    Student.findOne({ 
      'worksnap.user.user_id': item.user_id[0] 
     }) 
     .populate('user') 
     .exec(function (err, student) { 
      if (err) { 
       throw err; 
      } 
      student.timeEntries.push(item); 
      student.save(function (err) { 
       if (err) { 
        console.log(err); 
       } else { 
        console.log(Math.random()); 
       } 
      }); 

     }); 
} 

function promisedRequest(requestOptions) { 
    //create a deferred object from Q 
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 
    var deferred = Q.defer(); 
    var req = http.request(requestOptions, function (response) { 
     //set the response encoding to parse json string 
     response.setEncoding('utf8'); 
     var responseData = ''; 
     //append data to responseData variable on the 'data' event emission 
     response.on('data', function (data) { 
      responseData += data; 
     }); 
     //listen to the 'end' event 
     response.on('end', function() { 
      //resolve the deferred object with the response 
      console.log('http call finished'); 
      deferred.resolve(responseData); 
     }); 
    }); 

    //listen to the 'error' event 
    req.on('error', function (err) { 
     //if an error occurs reject the deferred 
     deferred.reject(err); 
    }); 
    req.end(); 
    //we are returning a promise object 
    //if we returned the deferred object 
    //deferred object reject and resolve could potentially be modified 
    //violating the expected behavior of this function 
    return deferred.promise; 
} 

Es scheint auch, dass die saveEntry() innerhalb .then wird sofort für alle Schüler aufgerufen wird, und das scheint problematisch.

Ich bin neu in Javascript besonders wenn es um Versprechen kommt, Rückrufe ... jemand eine Idee zu erreichen, so etwas hat ...

+0

Was ist 'parser.parseString()'? Ist das eine synchrone oder asynchrone Operation? – jfriend00

+1

Sie sollten auch wissen, dass die Verwendung von 'throw' innerhalb eines asynchronen Callbacks absolut nicht gut ist, es sei denn, es befindet sich in einem' 'then()' Handler. Es wirft sich einfach in die Eingeweide einer asynchronen Operation zurück und Sie können es nie irgendwo finden. Verwenden Sie in diesem Fall keinen Wurf. – jfriend00

+0

Warum auch '_.forEach (timeEntry)'? Was ist an diesem Punkt "timeEntry"? – jfriend00

Antwort

0

Zunächst einmal, mehrere verschachtelte Operationen werden viel leichter sein, Fehler zuverlässig zu codieren und zu handhaben, wenn Sie verspricht für alle Asynchron-Operationen verwenden. Das bedeutet, dass Sie lernen, wie Sie die in Ihrer Datenbank enthaltenen Versprechen nutzen (ich nehme an, Sie benutzen Mungo) und wickeln Sie dann alle anderen asynchronen Operationen ein, um Versprechungen zu verwenden. Hier sind ein paar Links zur Verwendung von Versprechen mit Mungo:

Switching to use promises in Mongoose

Plugging in your own promise library into Mongoose

Also, an der Mungo Dokumentation suchen, können Sie sehen, dass .exec() und .save() bereits Versprechungen zurückkehren, damit wir sie direkt verwenden.

diese Codezeile hinzufügen, wird Mongoose sagen Q Versprechungen zu verwenden (da, dass das Versprechen Bibliothek ist Ihnen zeigen):

// tell mongoose to use Q promises 
mongoose.Promise = require('q').Promise; 

Dann müssen Sie einige Operationen promisify, die Versprechungen wie die nicht verwenden Analyseschritt:

function parse(r) { 
    var deferred = Q.defer(); 
    parser.parseString(r, function(err, results) { 
     if (err) { 
      deferred.reject(err); 
     } else { 
      deferred.resolve(results); 
     } 
    }); 
    return deferred.promise; 
} 

saveTimeEntry() leicht geschrieben werden kann, ein Versprechen zurück, nur das Versprechen Unterstützung mit bereits in der Datenbank:

function saveTimeEntry(item) { 
    return Student.findOne({'worksnap.user.user_id': item.user_id[0]}).populate('user').exec().then(function(student) { 
     student.timeEntries.push(item); 
     return student.save(); 
    }); 
} 

So, jetzt haben Sie alle richtigen Stücke, um die Hauptlogik mit Versprechungen neu zu schreiben. Das Wichtigste, an das Sie sich erinnern sollten, ist, dass, wenn Sie ein Versprechen von einem -Handler zurückgeben, dieses Versprechen auf das Elternversprechen kettet. Wir werden das viel in Ihrer Verarbeitung verwenden. Ferner ist ein gemeinsames Design-Muster für die Sequenzierung Versprechungen, die durch ein Array iterieren array.reduce() wie folgt zu verwenden:

return array.reduce(function(p, item) { 
    return p.then(function() { 
     return someAsyncPromiseOperation(item); 
    }); 
}, Q()); 

Wir diese Struktur in ein paar Orten verwenden, wird die Kernlogik mit Versprechungen zu umschreiben:

// tell mongoose to use Q promises 
mongoose.Promise = require('q').Promise; 

iterateThruAllStudents(from, to).then(function() { 
    // done successfully here 
}, function(err) { 
    // error occurred here 
}); 

function iterateThruAllStudents(from, to, callback) { 
    return Student.find({status: 'student'}).populate('user').exec().then(function (students) { 
     // iterate through the students array sequentially 
     students.reduce(function(p, student) { 
      return p.then(function() { 
       if (student.worksnap.user != null) { 
        var worksnapOptions = { 
         hostname: 'worksnaps.com', 
         path: '/api/projects/' + project_id + '/time_entries.xml?user_ids=' + student.worksnap.user.user_id + 
           '&from_timestamp=' + from + '&to_timestamp=' + to, 
         headers: {'Authorization': 'Basic xxx='}, 
         method: 'GET' 
        }; 

        return promisedRequest(worksnapOptions).then(function(response) { 
         return parse(response).then(function(results) { 
          // assuming results.time_entries is an array 
          return results.time_entries.reduce(function(p, item) { 
           return p.then(function() { 
            return saveTimeEntry(item); 
           }); 
          }, Q()); 
         }); 
        }); 
       } 
      }); 
     }, Q()) 
    }); 
} 

function parse(r) { 
    var deferred = Q.defer(); 
    parser.parseString(r, function(err, results) { 
     if (err) { 
      deferred.reject(err); 
     } else { 
      deferred.resolve(results); 
     } 
    }); 
    return deferred.promise; 
} 

function saveTimeEntry(item) { 
    return Student.findOne({'worksnap.user.user_id': item.user_id[0]}).populate('user').exec().then(function(student) { 
     student.timeEntries.push(item); 
     return student.save(); 
    }); 
} 

function promisedRequest(requestOptions) { 
    //create a deferred object from Q 
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 
    var deferred = Q.defer(); 
    var req = http.request(requestOptions, function (response) { 
     //set the response encoding to parse json string 
     response.setEncoding('utf8'); 
     var responseData = ''; 
     //append data to responseData variable on the 'data' event emission 
     response.on('data', function (data) { 
      responseData += data; 
     }); 
     //listen to the 'end' event 
     response.on('end', function() { 
      //resolve the deferred object with the response 
      console.log('http call finished'); 
      deferred.resolve(responseData); 
     }); 
    }); 

    //listen to the 'error' event 
    req.on('error', function (err) { 
     //if an error occurs reject the deferred 
     deferred.reject(err); 
    }); 
    req.end(); 
    return deferred.promise; 
} 

Eine Sache, die dieses anbietet, ist, dass Ihr Code nicht ist, dass IRGENDEIN Fehler, der auftritt, den ganzen Weg zurück zu der zurückgekehrten Verheißung von iterateThruAllStudents() percoleate, also werden keine Fehler versteckt.


Und Reinigung es dann auf einige verschachtelte Vertiefung zu reduzieren und eine nützliche Utility-Funktion hinzugefügt, es sieht dies wie:

// tell mongoose to use Q promises 
mongoose.Promise = require('q').Promise; 

// create utility function for promise sequencing through an array 
function sequence(array, iterator) { 
    return array.reduce(function(p, item) { 
     return p.then(function() { 
      return iterator(item); 
     }); 
    }, Q()); 
} 

iterateThruAllStudents(from, to).then(function() { 
    // done successfully here 
}, function(err) { 
    // error occurred here 
}); 

function iterateThruAllStudents(from, to, callback) { 
    return Student.find({status: 'student'}).populate('user').exec().then(function (students) { 
     // iterate through the students array sequentially 
     return sequence(students, function(item) { 
      if (student.worksnap.user != null) { 
       var worksnapOptions = { 
        hostname: 'worksnaps.com', 
        path: '/api/projects/' + project_id + '/time_entries.xml?user_ids=' + student.worksnap.user.user_id + 
          '&from_timestamp=' + from + '&to_timestamp=' + to, 
        headers: {'Authorization': 'Basic xxx='}, 
        method: 'GET' 
       }; 
       return promisedRequest(worksnapOptions).then(function(response) { 
        return parse(response); 
       }).then(function(results) { 
        // assuming results.time_entries is an array 
        return sequence(results.time_entries, saveTimeEntry); 
       }); 
      } 
     }); 
    }); 
} 

function parse(r) { 
    var deferred = Q.defer(); 
    parser.parseString(r, function(err, results) { 
     if (err) { 
      deferred.reject(err); 
     } else { 
      deferred.resolve(results); 
     } 
    }); 
    return deferred.promise; 
} 

function saveTimeEntry(item) { 
    return Student.findOne({'worksnap.user.user_id': item.user_id[0]}).populate('user').exec().then(function(student) { 
     student.timeEntries.push(item); 
     return student.save(); 
    }); 
} 

function promisedRequest(requestOptions) { 
    //create a deferred object from Q 
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 
    var deferred = Q.defer(); 
    var req = http.request(requestOptions, function (response) { 
     //set the response encoding to parse json string 
     response.setEncoding('utf8'); 
     var responseData = ''; 
     //append data to responseData variable on the 'data' event emission 
     response.on('data', function (data) { 
      responseData += data; 
     }); 
     //listen to the 'end' event 
     response.on('end', function() { 
      //resolve the deferred object with the response 
      console.log('http call finished'); 
      deferred.resolve(responseData); 
     }); 
    }); 

    //listen to the 'error' event 
    req.on('error', function (err) { 
     //if an error occurs reject the deferred 
     deferred.reject(err); 
    }); 
    req.end(); 
    //we are returning a promise object 
    //if we returned the deferred object 
    //deferred object reject and resolve could potentially be modified 
    //violating the expected behavior of this function 
    return deferred.promise; 
} 

Natürlich, das ist eine Menge Code, und ich habe keine Um es zu testen und ich habe vorher noch nie einen Mungo-Code geschrieben, also könnte es hier sehr gut gehen, aber hoffentlich könnt ihr die generelle Idee sehen und Fehler durcharbeiten, die ich vielleicht gemacht habe.

+0

Nun, das ist wirklich viel besser als ich ... Ich schätze deine Mühe und die Zeit, die du dir gegeben hast, um dieses Problem zu betrachten Ich hoffe, dass es auch allen anderen geholfen hat, außer mir ... Ich habe meinen Code wie urs geändert und es scheint zu funktionieren alles gut :) –

0

Ok ich dieses Problem gelöst haben und wenn jemand in Zukunft hat dieses Problem Hier ist eine Lösung, aus der ich gekommen bin.

var startDate = new Date("February 20, 2016 00:00:00"); //Start from February 
var from = new Date(startDate).getTime()/1000; 
startDate.setDate(startDate.getDate() + 30); 
var to = new Date(startDate).getTime()/1000; 

iterateThruAllStudents(from, to); 

function iterateThruAllStudents(from, to) { 
    Student.find({status: 'student'}) 
     .populate('user') 
     .exec(function (err, students) { 
      if (err) { 
       throw err; 
      } 

      var counter = 1; 
      async.eachSeries(students, function iteratee(student, callback) { 
       counter++; 
       if (student.worksnap.user != null) { 
        console.log(''); 
        console.log('--------------'); 
        console.log(student.worksnap.user.user_id); 
        var worksnapOptions = { 
         hostname: 'worksnaps.com', 
         path: '/api/projects/' + project_id + '/time_entries.xml?user_ids=' + student.worksnap.user.user_id + '&from_timestamp=' + from + '&to_timestamp=' + to, 
         headers: { 
          'Authorization': 'Basic xxxxx' 
         }, 
         method: 'GET' 
        }; 

        promisedRequest(worksnapOptions) 
         .then(function (response) { //callback invoked on deferred.resolve 
          parser.parseString(response, function (err, results) { 
           var json_string = JSON.stringify(results.time_entries); 
           var timeEntries = JSON.parse(json_string); 
           var isEmpty = _.isEmpty(timeEntries); // true 
           if (isEmpty) { 
            callback(null); 
           } 
           saveTimeEntry(timeEntries).then(function (response) { 
            console.log('all timeEntries for one student finished....Student: ' + student.worksnap.user.user_id + ' Student Counter: ' + counter); 
            callback(null); 
           }); 
          }); 
         }, function (newsError) { //callback invoked on deferred.reject 
          console.log(newsError); 
         }); 
       } else { 
        callback(null); 
       } 
      }); 
     }); 
} 

function saveTimeEntry(timeEntries) { 
    var deferred = Q.defer(); 
    _.forEach(timeEntries, function (timeEntry) { 
     _.forEach(timeEntry, function (item) { 
      Student.findOne({ 
        'worksnap.user.user_id': item.user_id[0] 
       }) 
       .populate('user') 
       .exec(function (err, student) { 
        if (err) { 
         //throw err; 
         console.log(err); 
        } 
        student.timeEntries.push(item); 
        student.save(function (err) { 
         if (err) { 
          console.log(err); 
          deferred.reject(err); 
         } else { 
          //console.log(Math.random()); 
         } 
        }); 

       }); 
     }); 
     deferred.resolve('finished saving timeEntries for one student...'); 
    }); 

    return deferred.promise; 
} 

function promisedRequest(requestOptions) { 
    //create a deferred object from Q 
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 
    var deferred = Q.defer(); 
    var req = http.request(requestOptions, function (response) { 
     //set the response encoding to parse json string 
     response.setEncoding('utf8'); 
     var responseData = ''; 
     //append data to responseData variable on the 'data' event emission 
     response.on('data', function (data) { 
      responseData += data; 
     }); 
     //listen to the 'end' event 
     response.on('end', function() { 
      //resolve the deferred object with the response 
      console.log('http call finished'); 
      deferred.resolve(responseData); 
     }); 
    }); 

    //listen to the 'error' event 
    req.on('error', function (err) { 
     //if an error occurs reject the deferred 
     console.log('inside On error.'); 
     console.log(err); 
     deferred.reject(err); 
    }); 
    req.end(); 
    //we are returning a promise object 
    //if we returned the deferred object 
    //deferred object reject and resolve could potentially be modified 
    //violating the expected behavior of this function 
    return deferred.promise; 
} 
+0

Sie vermissen viel Fehlerbehandlung, um Fehler richtig zu erkennen und zu behandeln. Regelmäßige Rückrufe und Versprechen zu mischen, ist eigentlich nie eine gute Idee. Ein guter Entwurf wäre hier, alle asynchronen Operationen auf Versprechungen umzustellen und dann die gesamte asynchrone Logik mit Versprechungen zu schreiben. Ich würde das für dich tun, aber du hast die Fragen nicht beantwortet, die ich in Kommentaren gestellt habe, also kann ich nicht. – jfriend00

+0

@ jfriend00, Wenn Sie eine bessere Lösung haben, bitte posten Sie, damit ich es als beste Antwort akzeptieren kann ... Dies war nur eine Lösung, die für mich arbeitete, aber nicht so sauber, wie es getan werden sollte .... –

+0

Ich ging mehrere Kommentare, in denen Sie Fragen stellen, auf die ich die Antworten wissen muss, um Ihnen eine bessere Antwort zu geben. Ich habe es versucht, bin aber auf fehlende Informationen gestoßen, um eine Antwort zu erstellen. Ich verstehe nicht, warum du meine Fragen nicht beantwortest. – jfriend00