2016-10-19 3 views
5

ich eine Express-App haben, die ihre Daten von einem externen APIerstellen Modelle in Express.js

api.com/companies (GET, POST) 
api.com/companies/id (GET, PUT) 

ich ein Modell erstellen möchten wird der Code leichter zu pflegen, wie Sie bin ich zu wiederholen viel sehen von Code hier.

router.get('/companies', function(req, res, next) { 

    http.get({ 
     host: 'http://api.com', 
     path: '/companies' 
    }, function(response) { 
     var body = ''; 
     response.on('data', function(d) { 
      body += d; 
     }); 
    }); 

    res.render('companies', {data: body}); 
}); 

router.get('/companies/:id', function(req, res, next) { 

    http.get({ 
     host: 'http://api.com', 
     path: '/companies/' + req.params.id 
    }, function(response) { 
     var body = ''; 
     response.on('data', function(d) { 
      body += d; 
     }); 
    }); 

    res.render('company', {data: body}); 
}); 

Wie kann ich das tun?

Antwort

1

Zunächst einmal: http.get ist asynchron. Faustregel: Wenn Sie einen Rückruf sehen, handelt es sich um eine asynchrone Funktion. Sie können nicht feststellen, ob das http.get() und sein Rückruf abgeschlossen werden, wenn Sie die Anfrage mit res.render beenden. Das bedeutet, dass res.render immer innerhalb des Callbacks passieren muss.

Ich verwende ES6 Syntax in diesem Beispiel.

// request (https://github.com/request/request) is a module worthwhile installing. 
const request = require('request'); 
// Note the ? after id - this is a conditional parameter 
router.get('/companies/:id?', (req, res, next) => { 

    // Init some variables 
    let url = ''; 
    let template = '' 

    // Distinguish between the two types of requests we want to handle 
    if(req.params.id) { 
     url = 'http://api.com/companies/' + req.params.id; 
     template = 'company'; 
    } else { 
     url = 'http://api.com/companies'; 
     template = 'companies'; 
    } 

    request.get(url, (err, response, body) => { 

     // Terminate the request and pass the error on 
     // it will be handled by express error hander then 
     if(err) return next(err); 
     // Maybe also check for response.statusCode === 200 

     // Finally terminate the request 
     res.render(template, {data: body}) 
    }); 

}); 

In Bezug auf Ihre 'Modell' Frage. Ich würde sie eher "Dienste" nennen, da ein Modell eine Sammlung von Daten ist. Ein Service ist eine Sammlung von Logik.

Um ein Unternehmen Servicemodul tun so etwas zu schaffen:

// File companyService.js 
const request = require('request'); 

// This is just one of many ways to encapsulate logic in JavaScript (e.g. classes) 
// Pass in a config that contains your service base URIs 
module.exports = function companyService(config) { 
    return { 
     getCompanies: (cb) => { 
      request.get(config.endpoints.company.many, (err, response, body) => { 
       return cb(err, body); 
      }); 
     }, 
     getCompany: (cb) => { 
      request.get(config.endpoints.company.one, (err, response, body) => { 
       return cb(err, body); 
      }); 
     }, 
    } 
}; 


// Use this module like 
const config = require('./path/to/config'); 
const companyService = require('./companyService')(config); 

// In a route 
companyService.getCompanies((err, body) => { 
    if(err) return next(err); 

    res.render(/*...*/) 
}); 
1

Die allgemeine Vorgehensweise beim Refactoring besteht darin, zu identifizieren, was in Ihrem Code anders ist, und diesen als dynamischen Teil zu extrahieren, der an eine Funktion übergeben wird, die den gemeinsamen Code enthält.

Zum Beispiel sind die zwei Dinge, die hier verschieden sind, der Weg, auf dem die Anfragen passieren

'/companies' vs '/companies/:id' 

und die damit verbundene Weg, der zu http.get

'/companies' vs '/companies/' + req.params.id 

Sie diese übergeben wird extrahieren und Übergeben Sie sie an eine Funktion, die Ihnen einen Handler zuweisen wird.

Hier ist ein allgemeiner Ansatz:

// props contains the route and 
// a function that extracts the path from the request 
function setupGet(router, props) { 
    router.get('/' + props.route, function(req, res, next) { 

    http.get({ 
     host: 'http://api.com', 
     path: props.getPath(req) 
    }, function(response) { 
     var body = ''; 
     response.on('data', function(d) { 
     body += d; 
     }); 
    }); 

    res.render('company', { 
     data: body 
    }); 
    }); 
} 

Und es dann mit den beiden Optionen nennen:

setupGet(router, { 
    route: 'companies', 
    getPath: function(req) { 
    return 'companies'; 
    } 
}); 

setupGet(router, { 
    route: 'companies/:id', 
    getPath: function(req) { 
    return 'companies' + req.params.id; 
    } 
}); 

Der Vorteil hierbei ist, dass Sie eine beliebige Kombination von Routen und Wege, wie die Verwendung als gut nutzen können andere req Eigenschaften, um den Pfad zu bestimmen.

Eine andere Sache, die Sie erkennen müssen, ist, dass Ihr res.render Anruf passieren wird, bevor Sie body += d tun, weil der ehemalige synchron direkt nach dem Aufruf http.get geschieht und diese geschieht asynchron (etwas später).

Wahrscheinlich möchten Sie die Methode render in den Callback selbst setzen.

// props contains the route and 
// a function that extracts the path from the request 
function setupGet(router, props) { 
    router.get('/' + props.route, function(req, res, next) { 

    http.get({ 
     host: 'http://api.com', 
     path: props.getPath(req) 
    }, function(response) { 
     var body = ''; 
     response.on('data', function(d) { 
     body += d; 

     // probably want to render here 
     res.render('company', { 
      data: body 
     }); 
     }); 
    }); 
    }); 
} 
+0

guten Punkt. Ich gewöhne mich immer noch an dieses asynchrone Zeug Ich werde nicht in der Lage sein, ein Modell zu erstellen, so dass ich/companies in mehr als einer Seite anrufen kann? Ich frage, weil der Async die Anfrage-Antwort vervollständigen muss, um Daten zu erhalten. Jedes Mal, wenn ich alle Firmen anzeigen muss, muss ich eine HTTP-Anfrage machen und die Daten im Callback senden? – handsome

+0

Nun, wenn die Unternehmen nicht ändern, können Sie sie irgendwo im Speicher speichern und die Anfrage erst machen, die Firmen speichern, und dann jedes Mal danach den gespeicherten Wert zurückgeben. Soll ich meine Frage bearbeiten und Ihnen ein Beispiel zeigen? – nem035

+0

das macht Sinn, aber ich bin auf der Suche nach etwas mehr modellorientiert, wie die gute alte PHP, so dass ich Logik wiederverwenden kann, anstatt all diese http Anrufe überall. Danke, Mann! – handsome

2

In diesem Fall müssen nicht mehrere Routen verwendet werden! Sie können einen optionalen Parameter mit verwenden?. Werfen Sie einen Blick auf das folgende Beispiel:

router.get('/companies/:id?', function(req, res, next) { 
    var id = req.params.id; 

    http.get({ 
     host: 'http://api.com', 
     path: '/companies/' + id ? id : "" 
    }, function(response) { 
     var body = ''; 
     response.on('data', function(d) { 
      body += d; 
     }); 
    }); 

    res.render('companies', {data: body}); 
}); 

Das Stück Code hier:

path: '/companies/' + id ? id : "" 

wird unter Verwendung eines Inline-if-Anweisung, so, was es sagen will, ist, wennid != null, false, or undefined, fügen ID der companies/ Zeichenfolge und sonst einfach nichts hinzufügen.

bearbeiten

In Bezug auf js Klassen, Sie so etwas tun könnte:

router.get('/companies/:id?', function(req, res, next) { 
    var id = req.params.id; 
    var company = new Companies(id); 
    company.get(() => { 
     // We now have the response from our http.get 
     // So lets access it now! 
     // Or run company.put(function() {...}) 
     console.log (company.body); 
     res.render('companies', {data: company.body}); 
    }); 

}); 

Ich habe hinzugefügt Rückrufe:

// Seperate into a different file and export it 
class Companies { 
    constructor (id) { 
     this.id= id; 
     this.body = ""; 
     // You can add more values for this particular object 
     // Or you can dynamically create them without declaring here 
     // e.g company.new_value = "value" 
    } 

    get (cb) { 
     http.get({ 
      host: 'http://api.com', 
      path: '/companies/' + this.id ? this.id : "" 
     }, function(response) { 
      response.on('data',(d) => { 
       this.body += d; 
       cb(); // callback 
      }); 
     }); 
    } 

    post() { 
     // You can add more methods ... E.g a POST method. 
    } 
    put (cb) { 
     http.put({ 
      host: 'http://api.com', 
      path: '/companies/' + this.id ? this.id : "", 
      ... Other object values here ... 
     }, function(response) { 
      response.on('data',(d) => { 
       ... do something here with the response ... 
       cb(); //callback 
      }); 
     }); 
    } 
} 

Ihr Router Klasse dann diese Klasse wie so verwenden könnte hier für die Einfachheit, aber ich empfehle die Verwendung von Versprechen: https://developers.google.com/web/fundamentals/getting-started/primers/promises

+0

yup du hast Recht, das kann mit deinem Ansatz sehr vereinfacht werden Ich war auf der Suche nach etwas wie 'var data = new companies()' oder 'var data = new companies (1)' und diese Firmen() werden die Magie machen, um die HTTP-Anfrage zu machen und die Daten zu bekommen. damit ich es an mehreren Orten wiederverwenden kann. Vielen Dank!! – handsome

+0

Keine Sorgen und viel Glück :) Ja, Sie können diese Logik in eine Klasse relativ einfach implementieren! Ich werde versuchen, ein kleines Beispiel hinzuzufügen. Denken Sie daran, + 1/akzeptieren, wenn es geholfen hat: D @handsome – James111

+0

@handsome Werfen Sie einen Blick auf mein Update. Es verwendet Knoten es6 Klassen :) – James111