2016-07-16 10 views
0

Ich füge eine sehr einfache Login-Methode zu einem Bookshelf-Benutzermodell in einer ExpressJS-App, aber ich kann nicht die Fehler von der abgelehnten Versprechen, dass die Login-Funktion im Benutzermodell zurückgibt . Ich schaue auf Bookshelf Beispiel Beispiel Login in der Dokumentation unter http://bookshelfjs.org/#Model-static-extend, aber dieses Beispiel verwendet Bluebird, während ich versuche, das gleiche mit den integrierten ES6 verspricht.Abfangen von Fehlern mit ES6 Versprechen und BookshelfJS

Mein Login-Methode im Benutzermodell:

userModel.js

function login(email, password) { 
    return new Promise((resolve, reject) => { 
    User.where('email', email) 
     .fetch({ require: true }) 
     .then(user => { 
     bcrypt.compare(password, user.get('password'), (err, matched) => { 
      if (!matched) return reject(new Error('Password didn\'t match!')); 
      resolve(user); 
     }); 
     }); 
    }); 

Die Aktion-Controller, der Login implementiert und ruft User.login aus dem Bücherregal User Modell:

usersAuthController. js

function logUserIn(req, res) { 
    new User().login(req.body.email, req.body.password) 
    .then(user => res.json({ message: 'Login succeeded!' })) 
    .catch(User.NotFoundError,() => res.status(404).json({ error: 'User not found!' }) // catch #1 
    .catch(err => res.status(401).json({ err: err.message })); // catch #2 
} 

Meine Absicht ist, dass login() eine abgelehnte Versprechen zurückgeben kann, wenn Bookshelf User.fetch Methode den Benutzer mit der angegebenen E-Mail nicht finden kann. In diesem Fall sollte die Zeile .catch(User.NotFoundError ...) (catch # 1) es fangen und eine 404 zurückgeben. Ich beabsichtige auch login(), eine zurückgewiesene Zusage zurückzugeben, wenn bcrypt feststellt, dass das an login() übergebene Kennwort nicht mit dem Kennwort des Benutzers übereinstimmt "catch-all" (Fang # 2) unter der User.NotFoundError catch-Anweisung zurückgeben sollte ein 401.

Wenn ich in dem falschen Passwort setzen, die logUserIn() Controller-Aktion in dem obigen Code geht { error: "Cannot set property 'message' of undefined" } # 2 mit der Fehlermeldung zu fangen anstelle der Nachricht 'Password didn't match!' Nachricht, die ich in login() abgelehnt habe. Wenn ich eine nicht vorhandene E-Mail einfüge, wird die Antwort nie gesendet und der Fehler Unhandled rejection CustomError: EmptyResponse wird in der Konsole ausgelöst. Nur gültige Eingabe funktioniert.

Ein Versuch zu beheben: fangen User.NotFoundError direkt im Modell statt.

Ich zog Fang # 1 zum Modell Benutzer, so dass die Login-Methode wie jetzt aussieht:

userModel.js

function login(email, password) { 
    return new Promise((resolve, reject) => { 
    User.where('email', email) 
     .fetch({ require: true }) 
     .then(user => { 
     bcrypt.compare(password, user.get('password'), (err, matched) => { 
      if (!matched) return reject(new Error('Password didn\'t match!')); 
      resolve(user); 
     }); 
     }) 
     .catch(User.NotFoundError,() => reject({ error: 'User not found!' })); 
    }); 

Auf diese Weise kann ich beiden Fehler (falsches Passwort fangen und nicht existierende Email) korrekt, aber so kann ich den Statuscode im Controller nicht angeben. Wenn der Benutzer mit der angegebenen E-Mail nicht gefunden werden konnte, sollte er eine 404 zurückgeben, aber wenn das Passwort falsch war, sollte es eine 401 zurückgeben, aber beide Fehler gehen an die Catch-All (Catch # 2) in der Controller-Aktion (die immer eine 401 zurückgibt).

Um dieses Problem, in dem User Modell lösen konnte ich .catch(User.NotFoundError,() => reject({ name: 'NotFoundError', message: 'User not found!' })) und in der Controller-Aktion tun kann ich überprüfen, welche Art von Fehlern, die ich bin mit const statusCode = err.name === 'NotFoundError' ? 404 : 401 bekommen, aber das scheint wirklich chaotisch und trifft nicht den Punkt diese .catch Aussagen des Habens .

Gibt es eine Möglichkeit, die User.NotFoundError von der Login-Methode des Modells und alle anderen Fehler alle in logInUser zu fangen? Warum funktioniert das Setup nicht zuerst, das mit beiden catch Anweisungen in usersAuthController.js, und was haben die Cannot set property 'message' of undefined' und CustomError: EmptyResponse Fehler bedeutet (hat es etwas damit zu tun, Bookshelf Bluebird Versprechungen gegenüber den eingebauten ES6 Versprechen zu vermischen)? Was ist der beste Weg, damit umzugehen?

+0

Vermeiden Sie die [ 'Promise' Konstruktor Antipattern] (http://stackoverflow.com/q/23803743/1048572)! Sie sollten nur 'bcrypt.compare' in dieser' login' Funktion promiden. – Bergi

Antwort

1

In Ihrer anfänglichen Implementierung werden keine Ablehnungen propagiert, die durch User.fetch() verursacht werden können. Auch weil User.fetch() bereits eine Zusage zurückgibt, ist das Umwickeln mit einer neuen Zusicherung ein bisschen ein Anti-Muster (obwohl Sie immer noch bcrypt.compare() mit einem Versprechen umhüllen müssen, da das nur mit Rückrufen funktioniert).

Versuchen Sie folgendes:

function login(email, password) { 
    return User .where('email', email) 
       .fetch({ require: true }) 
       .then(user => { 
       return new Promise((resolve, reject) => { 
        bcrypt.compare(password, user.get('password'), (err, matched) => { 
        if (err)  return reject(err); 
        if (!matched) return reject(new Error('Password didn\'t match!')); 
        resolve(user); 
        }); 
       }) 
       }); 
} 
+0

Danke! Nur zur Klärung, normalerweise wann sollte ich Sachen in eine Verheissung einpacken (wenn ich das verstehe, ist der Punkt der Rückkehr der Verheißung in '.dann' ist so, dass ich die'. – satray

+1

@satray Wenn etwas bereits ein Versprechen zurückgibt, müssen Sie es nicht umbrechen. In diesem Fall, weil "bcrypt.compare" kein Versprechen zurückgibt, wird es verpackt (aber "so spät wie möglich"). Es ist immer gut, Versprechen (und Versprechensketten) zurückzugeben, auch wenn Sie '.then()' und '.catch()' bereits behandeln. – robertklep

Verwandte Themen