2014-09-19 18 views
5

Dưới đây là một ví dụ về một cái gì đó tôi muốn đơn giản hóa:Tránh callback địa ngục trong các biến/Passing nodeJs chức năng bên trong

exports.generateUrl = function (req, res) { 
    var id = req.query.someParameter; 

    var query = MyMongooseModel.findOne({'id': id}); 
    query.exec(function (err, mongooseModel) { 
     if(err) { 
      //deal with it 
     } 

     if (!mongooseModel) { 
      generateUrl(Id, 
       function (err, text, url) { 
        if (err) { 
         res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err); 
         return; 
        } 
        var newMongooseModel = new AnotherMongooseModel(); 
        newMongooseModel.id = id; 

        newMongooseModel.save(function (err) { 
         if (err) { 
          res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err); 
         } else { 
          res.send({url: url, text: text}); 
         } 
        }); 
       }); 
     } else { 
      //deal with already exists 
     } 
    }); 
}; 

Tôi đã nhìn thấy khác SO trả lời mà họ cho bạn biết sử dụng hàm được đặt tên, nhưng không nói làm thế nào để đối phó với biến bạn muốn vượt qua hoặc sử dụng hàng đợi của jQuery. Tôi không có sự sang trọng của một trong hai.

Tôi hiểu rằng tôi có thể thay thế các chức năng ẩn danh của mình bằng các chức năng tên, nhưng sau đó tôi sẽ cần phải chuyển các biến xung quanh. Hàm bên trong của tôi truy cập res như thế nào nếu hàm được định nghĩa ở nơi khác?

Trả lời

10

Cốt lõi cho câu hỏi của bạn là:

tôi hiểu rằng tôi có thể thay thế các hàm ẩn danh của tôi bằng các hàm tên, nhưng sau đó tôi sẽ cần phải chuyển các biến xung quanh. Chức năng bên trong của tôi sẽ truy cập như thế nào ví dụ nếu hàm được định nghĩa ở nơi khác?

Câu trả lời là sử dụng nhà máy chức năng.

Nói chung, điều này:

function x (a) { 
    do_something(function(){ 
     process(a); 
    }); 
} 

có thể được chuyển đổi sang này:

function x (a) { 
    do_something(y_maker(a)); // notice we're calling y_maker, 
           // not passing it in as callback 
} 

function y_maker (b) { 
    return function() { 
     process(b); 
    }; 
} 

Trong đoạn mã trên, y_maker là một chức năng mà tạo ra một hàm (chúng ta hãy gọi của chức năng rằng mục đích "y "). Trong mã của riêng tôi, tôi sử dụng quy ước đặt tên .._maker hoặc generate_.. để biểu thị rằng tôi đang gọi một nhà máy chức năng. Nhưng đó chỉ là tôi và quy ước là không có cách nào tiêu chuẩn hay được chấp nhận rộng rãi trong tự nhiên.

Vì vậy, đối với mã của bạn, bạn có thể cấu trúc lại nó để:

exports.generateUrl = function (req, res) { 
    var id = req.query.someParameter; 

    var query = MyMongooseModel.findOne({'id': id}); 
    query.exec(make_queryHandler(req,res)); 
}; 

function make_queryHandler (req, res) { 
    return function (err, mongooseModel) { 
     if(err) { 
      //deal with it 
     } 
     else if (!mongooseModel) { 
      generateUrl(Id,make_urlGeneratorHandler(req,res)); 
     } else { 
      //deal with already exists 
     } 
}} 

function make_urlGeneratorHandler (req, res) { 
    return function (err, text, url) { 
     if (err) { 
      res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err); 
      return; 
     } 
     var newMongooseModel = new AnotherMongooseModel(); 
     newMongooseModel.id = id; 
     newMongooseModel.save(make_modelSaveHandler(req,res)); 
}} 

function make_modelSaveHandler (req, res) { 
    return function (err) { 
     if (err) res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err); 
     else res.send({url: url, text: text}); 
}} 

này flattens ra các callbacks lồng nhau. Là một lợi ích bổ sung, bạn có thể đặt tên đúng chức năng được yêu cầu. Mà tôi xem xét thực hành tốt.

Nó cũng có lợi thế bổ sung là nhanh hơn đáng kể so với khi sử dụng gọi lại ẩn danh (hoặc với gọi lại lồng nhau hoặc với lời hứa, mặc dù nếu bạn chuyển các hàm được đặt tên thành promise.then() thay vì các hàm ẩn danh thì bạn sẽ nhận được cùng lợi ích tăng tốc).Một câu hỏi SO trước đó (google-fu của tôi không làm tôi ngày hôm nay) thấy rằng các hàm được đặt tên cao gấp đôi tốc độ (nếu tôi nhớ chính xác nó nhanh hơn 5 lần) các hàm ẩn danh trong node.js.

+0

Trong khi câu hỏi này có câu trả lời thực sự tốt, tôi đã chọn câu trả lời này vì tôi nghĩ nó dẫn đến mã sạch nhất. Đó là ngay lập tức rõ ràng nơi mà các biến đến từ và nó là rất bằng phẳng. Cảm ơn! – Nepoxx

+1

Điều này trông giống như một giải pháp tốt để tránh địa ngục gọi lại. Có bất kỳ lý lẽ nào chống lại việc sử dụng điều này hay bất kỳ nhược điểm nào không? –

3

Sử dụng lời hứa. Sử dụng Q và mongoose-q nó sẽ cung cấp cho: một cái gì đó như thế:

exports.generateUrl = function (req, res) { 
    var id = req.query.someParameter; 
    var text = ""; 

    var query = MyMongooseModel.findOne({'id': id}); 
    query.execQ().then(function (mongooseModel) { 

     if (!mongooseModel) { 
      return generateUrl(Id) 

    }).then(function (text) { 
     var newMongooseModel = new AnotherMongooseModel(); 
     newMongooseModel.id = id; 
     text = text; 

     newMongooseModel.saveQ() 
    }).then(function (url) { 
     res.send({url: url, text: text}); 
    }).fail(function(err) { 
     res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err); 
    }); 
}; 
+0

Ồ, và nếu tôi cần tạo một truy vấn Mongoose khác trong một 'sau đó', thì tôi vẫn bị" kẹt "với một hàm lồng bổ sung. (Chỉnh sửa: đã xóa câu hỏi về các biến xếp tầng giữa các hàm) – Nepoxx

+0

Sau đó, bạn phải tạo một var trong phạm vi generateUrl. Xem câu trả lời được cập nhật với văn bản var – Vinz243

1

Chức năng được đặt tên sẽ được thực hiện trong cùng phạm vi mà các chức năng ẩn danh và có thể truy cập vào tất cả các biến bạn hiện đang sử dụng. Cách tiếp cận này sẽ làm cho mã của bạn ít lồng nhau và dễ đọc hơn (điều này là tốt) nhưng vẫn về mặt kỹ thuật là trong "địa ngục gọi lại". Cách tốt nhất để tránh các tình huống như thế này là bọc các thư viện không đồng bộ của bạn (giả sử chúng không cung cấp lời hứa) với thư viện lời hứa như Q. IMO, hứa hẹn cung cấp một hình ảnh rõ ràng hơn nhiều về đường dẫn thực thi.

Bạn có thể tránh tình trạng khó khăn vì không biết biến đến từ bằng cách gắn các tham số để hàm có tên của bạn sử dụng bind, ví dụ:

function handleRequest(res, err, text, url) { 
    if (err) { 
     res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err); 
     return; 
    } 
    var newMongooseModel = new AnotherMongooseModel(); 
    newMongooseModel.id = id; 

    newMongooseModel.save(function (err) { 
     if (err) { 
      res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err); 
     } else { 
      res.send({url: url, text: text}); 
     } 
    }); 
} 

... 
generateUrl(Id, handleRequest.bind(null, res)); 
+0

Mối quan tâm của tôi với các hàm được đặt tên là mặc dù chúng có thể truy cập cùng các biến, nhưng không rõ ràng khi đọc các hàm đó mà các biến đó đến từ đó. – Nepoxx

+0

Tôi đã cập nhật câu trả lời của mình để giải quyết câu hỏi này –

Các vấn đề liên quan