Kiedy używać next () i return next () w Node.js.

142

Scenariusz : rozważ, co następuje, to część kodu z aplikacji sieci Web węzła.

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

Problem : sprawdzam, który z nich wybrać, po prostu next()lub return next(). Powyższy przykładowy kod działa dokładnie tak samo dla obu i nie wykazał żadnej różnicy w wykonaniu.

Pytanie : Czy ktoś może rzucić światło na to, kiedy next()i kiedy używać return next()oraz jakąś ważną różnicę?

Amol M Kulkarni
źródło

Odpowiedzi:

152

Niektórzy ludzie zawsze piszą, return next()aby upewnić się, że wykonanie zatrzyma się po wyzwoleniu wywołania zwrotnego.

Jeśli tego nie zrobisz, ryzykujesz wywołanie oddzwonienia po raz drugi, co zwykle ma katastrofalne skutki. Twój kod jest w porządku, ale przepisałbym go jako:

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

    if(!id)
        return next();

    // do something
});

Oszczędza mi to poziom wcięcia, a kiedy czytam kod ponownie później, jestem pewien, że nie ma sposobu, aby nextzostał wywołany dwukrotnie.

Laurent Perrin
źródło
3
Czy podobna sytuacja res.redirect('/')miałaby miejsce return res.redirect('/')w przypadku tego typu sytuacji? Może po prostu lepiej jest zawsze pisać return przed instrukcjami res, aby uniknąć błędów w ustawianiu nagłówków po ich wysłaniu?
Adam D,
@AdamD Też chciałbym wiedzieć.
theprogrammer
@theprogrammer Tak, myślę, że ta odpowiedź odnosi się w ten sam sposób do takich rzeczy res.redirecti najlepiej byłoby z niej korzystać, chyba że masz inne zadania serwera, które chcesz wykonać po przekierowaniu użytkownika.
Adam D
198

Jak odpowiada @Laurent Perrin:

Jeśli tego nie zrobisz, ryzykujesz wywołanie wywołania zwrotnego po raz drugi, co zwykle ma katastrofalne skutki

Podam tutaj przykład, jeśli piszesz oprogramowanie pośredniczące w ten sposób:

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()
})

Dowiesz się, że wyjście w konsoli to:

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

Oznacza to, że po zakończeniu wszystkich funkcji oprogramowania pośredniego uruchamia poniższy kod next ().

Jeśli jednak użyjesz return next(), natychmiast wyskoczy wywołanie zwrotne, a poniższy kod return next()w wywołaniu zwrotnym będzie nieosiągalny.

PJCHENder
źródło
32
Jako osoba początkująca, expressta odpowiedź była dla mnie jaśniejsza niż inne odpowiedzi. Kciuki w górę!
mandarynka
1
Czy coś podobnego można powiedzieć o res.redirect('/')wersetach return res.redirect('/')w tego typu sytuacji? Może po prostu lepiej jest zawsze pisać returnprzed resinstrukcjami, aby uniknąć błędów w ustawianiu nagłówków po ich wysłaniu?
Adam D,
1
Dlaczego powinienem pisać kod po next ()? Czy nie jest oczywiste, że po zakończeniu zadania w oprogramowaniu pośrednim nic nie robię? @PJCHENder
Imran Pollob
1
@ImranPollob czasami zdarzają się błędy. Kiedy piszesz dużo kodu, ifs / elses / etc. Możesz zapomnieć o `` return next () ''
Jone Polvora
49

next()jest częścią oprogramowania pośredniego Connect . Wywołania zwrotne przepływu routera nie dbają o to, czy zwrócisz cokolwiek z funkcji, więc return next()i next(); return;jest w zasadzie to samo.

Jeśli chcesz zatrzymać przepływ funkcji, możesz użyć next(err)w następujący sposób

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'); 
    }
);

Prawie dużo next()jest używane do rozszerzania oprogramowania pośredniego Twoich żądań.

drinchev
źródło
1
Czy możemy przesłać parametr taki jak next('No ID'):?
Amol M Kulkarni
8
next('No ID')w rzeczywistości wysyła błąd, który przerwie przepływ.
drinchev
Użyj next (null, "somevalue"); W przypadku narzędzi takich jak async.waterfall przekazuje wartość do następnej funkcji. W przypadku złożonych serii interakcji opartych na danych zwykle przekazuję obiekt kontekstu między funkcjami. W ten sposób mogę tworzyć ogólne funkcje, które mogą być współużytkowane przez wiele punktów końcowych i kontrolować przepływ danych w kontekście
Chad Wilson
5
"więc return next () i next (); return; jest w zasadzie to samo." - właśnie to, co chciałem przeczytać. thx @drinchev
Nick Pineda
2
Obserwuję coś przeciwnego (podczas odpalania błędu): next (błąd) uruchamia następne oprogramowanie pośredniczące, ale nadal wykonuje kod; return next (błąd) po prostu przenosi wykonanie do następnego oprogramowania pośredniego. next (e) i return next (e) NIE są takie same.
Nickolodeon,
0

Najlepiej w ogóle go nie używać! Wyjaśniam i to też wyjaśniam.

Funkcja next (), która może mieć dowolną nazwę i zgodnie z konwencją, została ustawiona na next. Jest to pośrednio związane z operacjami (PUT, GET, DELETE, ...), które są zwykle wykonywane na tym samym zasobie URI, na przykład/ 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 ()...)

Teraz, jeśli spojrzysz na app.get, app.put i app.delete używają tego samego uri (/ user /: id), jedyną rzeczą, która je odróżnia, jest ich implementacja. Gdy żądanie jest wysyłane (req) express umieszcza żądanie jako pierwsze w app.get, jeśli jakakolwiek walidacja utworzona, ponieważ to żądanie nie jest dla tego kontrolera nie powiedzie się, przekazuje żądanie do app.put, który jest następną trasą w pliku te, i tak na. Jak widać na poniższym przykładzie.

    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()

   })

Problem polega na tym, że w końcu przekazujesz żądanie wszystkim kontrolerom z nadzieją, że istnieje taki, który robi to, co chcesz, poprzez walidację żądania. W końcu wszyscy kontrolerzy otrzymują coś, co nie jest dla nich :(.

Jak więc uniknąć problemu next () ?

Odpowiedź jest naprawdę prosta.

1- powinien istnieć tylko jeden identyfikator URI do identyfikacji zasobu

http: // IpServidor / colection /: resource / colection /: resource jeśli twój URI jest dłuższy, powinieneś rozważyć utworzenie nowego URI

Przykład http: // IpServidor / users / pepe / kontakt / contacto1

2-Wszystkie operacje na tym zasobie muszą być wykonywane z poszanowaniem idempotencji czasowników http (get, post, put, delete, ...), więc wywołanie URI ma naprawdę tylko jeden sposób wywołania

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

Więcej informacji [ https://docs.microsoft.com/es-es/azure/architecture/best-practices/api-design#organize-the-api-around-resources][1]

Zobaczmy kod! Konkretna implementacja, dzięki której unikamy użycia next ()!

W pliku 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 );

W pliku 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;

Teraz plik usersRoute.js robi to, czego oczekuje się od pliku o nazwie usersRoute, czyli zarządzania trasami URI / users /

// plik 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;

W ten sposób unikasz użycia next, odprzęgasz kod, zyskujesz na wydajności, rozwijasz SOLID, zostawiasz otwarte drzwi na ewentualną migrację do mikroserwisów i przede wszystkim jest łatwy do odczytania przez programistę.

David
źródło
3
To jest nieprawidłowe, app.get nie przejdzie do app.put, jak sugerujesz. Wywoływane są tylko pasujące żądania, więc jeśli metoda to GET, zostanie wywołane tylko oprogramowanie pośredniczące app.get. Oprogramowanie pośredniczące nie musi sprawdzać metody żądania. Twoja sugestia ignoruje podstawową funkcję ekspresową i zamiast tego implementuje własny routing. Ponadto twoja sugestia zakłada, że ​​routing jest jedynym oprogramowaniem pośredniczącym, którego będziesz używać, ponieważ nigdy nie zostanie przekazany nigdzie.
Ravenex
Niepoprawne informacje, zobacz powyższe odpowiedzi.
DDiamond
-3

Kolejny() :

Wywołanie tej funkcji wywołuje następną funkcję oprogramowania pośredniego w aplikacji. Funkcja next () nie jest częścią Node.js ani Express API, ale jest trzecim argumentem przekazywanym do funkcji oprogramowania pośredniego.

Manish Sharma
źródło