Quand utiliser next () et retourner next () dans Node.js

136

Scénario : considérez que ce qui suit est la partie du code d'une application Web de nœud.

app.get('/users/:id?', function(req, res, next){
    var id = req.params.id;
    if (id) {
        // do something
    } else {
        next(); //or return next();
    }
});

Problème : je suis en train de vérifier lequel utiliser juste next()ou return next(). L'exemple de code ci-dessus fonctionne exactement de la même manière pour les deux et n'a montré aucune différence d'exécution.

Question : Quelqu'un peut-il faire la lumière là-dessus, quand utiliser next()et quand utiliser return next()et une différence importante?

Amol M Kulkarni
la source

Réponses:

141

Certaines personnes écrivent toujours return next()pour s'assurer que l'exécution s'arrête après le déclenchement du rappel.

Si vous ne le faites pas, vous risquez de déclencher le rappel une deuxième fois plus tard, ce qui a généralement des résultats dévastateurs. Votre code est bien tel quel, mais je le réécrirais comme suit:

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

    if(!id)
        return next();

    // do something
});

Cela me sauve un niveau d'indentation, et quand je relis le code plus tard, je suis sûr qu'il n'y a aucun moyen d' nextêtre appelé deux fois.

Laurent Perrin
la source
2
Une chose similaire serait-elle vraie pour res.redirect('/')vs return res.redirect('/')dans ce type de situation? Il est peut-être préférable de toujours écrire return devant les instructions res pour éviter les erreurs de définition des en-têtes après leur envoi?
Adam D
187

Comme la réponse de @Laurent Perrin:

Si vous ne le faites pas, vous risquez de déclencher le rappel une deuxième fois plus tard, ce qui a généralement des résultats dévastateurs

Je donne un exemple ici si vous écrivez un middleware comme celui-ci:

app.use((req, res, next) => {
  console.log('This is a middleware')
  next()
  console.log('This is first-half middleware')
})

app.use((req, res, next) => {
  console.log('This is second middleware')
  next()
})

app.use((req, res, next) => {
  console.log('This is third middleware')
  next()
})

Vous découvrirez que la sortie dans la console est:

This is a middleware
This is second middleware
This is third middleware
This is first-half middleware

Autrement dit, il exécute le code ci-dessous next () une fois toutes les fonctions middleware terminées.

Cependant, si vous utilisez return next(), il sautera immédiatement le rappel et le code ci-dessous return next()dans le rappel sera inaccessible.

PJCHENder
la source
29
En tant que débutant, expresscette réponse m'a rendu les choses plus claires que les autres réponses. Pouces vers le haut!
mandarin
1
Une chose similaire serait-elle vraie pour res.redirect('/')vs return res.redirect('/')dans ce type de situation? Il est peut-être préférable de toujours écrire returndevant les resinstructions pour éviter les erreurs de définition des en-têtes après leur envoi?
Adam D
1
Pourquoi devrais-je écrire du code après next ()? N'est-il pas évident que je ne fais rien après avoir terminé ma tâche dans un middleware? @PJCHENder
Imran Pollob
1
@ImranPollob se produit parfois des erreurs. Lorsque vous écrivez beaucoup de code, ifs / elses / etc. Vous pouvez oublier `` retour suivant () ''
Jone Polvora
46

next()fait partie du middleware connect . Les rappels pour le flux du routeur ne se soucient pas de savoir si vous retournez quelque chose de vos fonctions, donc return next()et next(); return;c'est fondamentalement le même.

Si vous souhaitez arrêter le flux de fonctions, vous pouvez utiliser next(err)comme suit

app.get('/user/:id?', 
    function(req, res, next) { 
        console.log('function one');
        if ( !req.params.id ) 
            next('No ID'); // This will return error
        else   
            next(); // This will continue to function 2
    },
    function(req, res) { 
        console.log('function two'); 
    }
);

Pretty much next()est utilisé pour étendre le middleware de vos requêtes.

Drinchev
la source
1
Pouvons-nous envoyer des paramètres comme next('No ID'):?
Amol M Kulkarni
7
next('No ID')envoie en fait une erreur, ce qui interrompra le flux.
drinchev
Utilisez next (null, "somevalue"); Pour les outils comme async.waterfall, la valeur sera transmise à la fonction suivante. Pour les séries complexes d'interactions basées sur les données, je passe généralement un objet de contexte entre les fonctions. De cette façon, je peux créer des fonctions génériques qui peuvent être partagées entre plusieurs points de terminaison et contrôler le flux via des données dans le contexte
Chad Wilson
5
"donc return next () et next (); return; est fondamentalement le même." - juste ce que j'avais besoin de lire. thx @drinchev
Nick Pineda
1
J'observe le contraire (lors du déclenchement d'erreur): next (error) déclenche le prochain middleware, mais continue d'exécuter du code; return next (erreur) relègue simplement l'exécution au middleware suivant. next (e) et return next (e) ne sont PAS les mêmes.
Nickolodeon
0

Il vaut mieux ne pas l'utiliser du tout! J'explique, et c'est ce que je fais aussi l'expliquer.

La fonction next () qui peut avoir n'importe quel nom et par convention a été définie sur next. Il est indirectement lié aux opérations (PUT, GET, DELETE, ...) qui sont généralement effectuées sur la même ressource URI par exemple/ user /: id

app.get('/user/:id', function (req,res,next)...)
app.put('/user/:id', function (req,res,next)...)
app.delete('/user/:id', function (req,res,next)...)
app.post('/user/', function ()...)

Maintenant, si vous regardez app.get, app.put et app.delete utilisent le même uri (/ user /: id), la seule chose qui les différencie est leur implémentation. Lorsque la demande est faite (req) express place la req en premier dans app.get, si une validation que vous avez créée parce que cette demande n'est pas pour ce contrôleur échoue, elle transmet la demande à app.put qui est la prochaine route dans le fichier et ainsi sur. Comme vu dans l'exemple ci-dessous.

    app.get('/user/:id', function (req,res,next){

    if(req.method === 'GET')
    //whatever you are going to do
    else
      return next() //it passes the request to app.put

    //Where would GET response 404 go, here? or in the next one. 
    // Will the GET answer be handled by a PUT? Something is wrong here.

   })
    app.put('/user/:id', function (req,res,next){

    if(req.method === 'PUT')
    //whatever you are going to do
    else
      return next()

   })

Le problème, c'est qu'à la fin vous finissez par passer la req à tous les contrôleurs en espérant qu'il y en a un qui fait ce que vous voulez, via la validation de la req. À la fin, tous les contrôleurs finissent par recevoir quelque chose qui n'est pas pour eux :(.

Alors, comment éviter le problème de next () ?

La réponse est vraiment simple.

1- il ne devrait y avoir qu'un seul uri pour identifier une ressource

http: // IpServidor / colection /: resource / colection /: resource si votre URI est plus long que cela, vous devriez envisager de créer un nouvel uri

Exemple http: // IpServidor / users / pepe / contacts / contacto1

2-Toutes les opérations sur cette ressource doivent être effectuées en respectant l'idempotence des verbes http (get, post, put, delete, ...) donc l'appel à un URI n'a vraiment qu'une seule façon d'appeler

POST http://IpServidor/users/  //create a pepe user 
GET http://IpServidor/users/pepe  //user pepe returns   
PUT http://IpServidor/users/pepe  //update the user pepe 
DELETE http://IpServidor/users/pepe  //remove the user pepe

Plus d'infos [ https://docs.microsoft.com/es-es/azure/architecture/best-practices/api-design#organize-the-api-around-resources[1]

Voyons le code! L'implémentation concrète qui nous fait éviter l'utilisation de next ()!

Dans le fichier index.js

//index.js the entry point to the application also caller app.js
const express = require('express');
const app = express();

const usersRoute = require('./src/route/usersRoute.js');

app.use('/users', usersRoute );

Dans le fichier usersRoute.js

    //usersRoute.js
    const express = require('express');
    const router = express.Router();

    const getUsersController = require('../Controllers/getUsersController.js');
    const deleteUsersController = require('../Controllers/deleteUsersController.js');

    router.use('/:name', function (req, res) //The path is in /users/:name
    {
    switch (req.method)
    {
    case 'DELETE':
      deleteUsersController(req, res);
      break;
    case 'PUT':
     // call to putUsersController(req, res);
     break;
    case 'GET':
     getUsersController(req, res);
     break;
    default:
     res.status(400).send('Bad request');
    } });

router.post('/',function (req,res) //The path is in /users/
{
    postUsersController(req, res);
});

module.exports = router;

Maintenant, le fichier usersRoute.js fait ce qu'un fichier appelé usersRoute est censé faire, c'est-à-dire gérer les routes de l'URI / users /

// fichier getUsersController.js

//getUsersController.js
    const findUser= require('../Aplication/findUser.js');
    const usersRepository = require('../Infraestructure/usersRepository.js');

    const getUsersController = async function (req, res)
    {

       try{
          const userName = req.params.name;
        //...
          res.status(200).send(user.propertys())

        }catch(findUserError){
           res.status(findUserError.code).send(findUserError.message)
        }
    }
   module.exports = getUsersController;

De cette façon, vous évitez l'utilisation du suivant, vous découplez le code, vous gagnez en performance, vous développez SOLID, vous laissez la porte ouverte à une éventuelle migration vers des microservices et surtout, il est facile à lire par un programmeur.

David
la source
2
Ceci est incorrect, app.get ne passera pas à app.put comme vous le suggérez. Seules les requêtes correspondantes sont appelées ainsi si la méthode est GET, seul le middleware app.get sera appelé. L'intergiciel n'a pas besoin de vérifier la méthode de requête. Votre suggestion ignore une fonction principale d'express et implémente à la place votre propre routage. De plus, votre suggestion suppose que votre itinéraire est le seul middleware que vous utiliserez car il n'est jamais transmis nulle part.
Ravenex
Informations incorrectes, veuillez consulter les réponses ci-dessus.
DDiamond le
-3

Suivant() :

L'appel de cette fonction appelle la fonction middleware suivante dans l'application. La fonction next () ne fait pas partie de Node.js ou de l'API Express, mais est le troisième argument qui est passé à la fonction middleware.

Manish Sharma
la source