Cách tránh lồng dài các hàm không đồng bộ trong Node.js


158

Tôi muốn tạo một trang hiển thị một số dữ liệu từ DB, vì vậy tôi đã tạo một số chức năng lấy dữ liệu đó từ DB của mình. Tôi chỉ là một người mới trong Node.js, theo như tôi hiểu, nếu tôi muốn sử dụng tất cả chúng trong một trang duy nhất (phản hồi HTTP) tôi sẽ phải lồng tất cả chúng:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

Nếu có nhiều chức năng như vậy, thì việc lồng nhau trở thành một vấn đề .

Có cách nào để tránh điều này không? Tôi đoán nó có liên quan đến cách bạn kết hợp nhiều chức năng không đồng bộ, dường như là một cái gì đó cơ bản.


12
Vậy khi bạn có 10 chức năng async, bạn có 10 cấp độ thụt đầu dòng?
Kay Pale

Liên kết này có thể giúp đỡ. stackoverflow.com/a/4631909/290340
Evan Plaice

1
Một vấn đề khác: chèn một chức năng khác giữa getSomeDategetSomeOtherDatecuối cùng là thay đổi thụt dòng của nhiều dòng khiến lịch sử git khó đọc hơn ( git blamethậm chí là vô dụng sau điều này) và bạn có thể mắc lỗi khi thực hiện thủ công
Daniel Alder

Câu trả lời:


73

Quan sát thú vị. Lưu ý rằng trong JavaScript, bạn thường có thể thay thế các hàm gọi lại ẩn danh nội tuyến bằng các biến hàm được đặt tên.

Sau đây là

http.createServer(function (req, res) {
   // inline callback function ...

   getSomeData(client, function (someData) {
      // another inline callback function ...

      getMoreData(client, function(moreData) {
         // one more inline callback function ...
      });
   });

   // etc ...
});

Có thể được viết lại để trông giống như thế này:

var moreDataParser = function (moreData) {
   // date parsing logic
};

var someDataParser = function (someData) {
   // some data parsing logic

   getMoreData(client, moreDataParser);
};

var createServerCallback = function (req, res) {
   // create server logic

   getSomeData(client, someDataParser);

   // etc ...
};

http.createServer(createServerCallback);

Tuy nhiên, trừ khi bạn có kế hoạch tái sử dụng logic gọi lại ở những nơi khác, việc đọc các hàm ẩn danh nội tuyến thường dễ dàng hơn nhiều, như trong ví dụ của bạn. Nó cũng sẽ giúp bạn không phải tìm tên cho tất cả các cuộc gọi lại.

Ngoài ra, lưu ý rằng như @pst đã lưu ý trong một nhận xét bên dưới, nếu bạn đang truy cập các biến đóng trong các hàm bên trong, thì ở trên sẽ không phải là một bản dịch đơn giản. Trong những trường hợp như vậy, sử dụng các hàm ẩn danh nội tuyến thậm chí còn thích hợp hơn.


26
Tuy nhiên, (và thực sự chỉ để hiểu sự đánh đổi) khi không lồng nhau, một số ngữ nghĩa đóng đối với các biến thể bị mất để nó không phải là bản dịch trực tiếp. Trong ví dụ trên, quyền truy cập vào 'res' in getMoreDatabị mất.

2
Tôi nghĩ rằng giải pháp của bạn bị hỏng: someDataParser thực sự phân tích TẤT CẢ dữ liệu, vì nó cũng gọi getMoreData. Theo nghĩa đó, tên hàm không chính xác và rõ ràng là chúng ta chưa thực sự loại bỏ vấn đề lồng nhau.
Konstantin Schubert

63

Kay, chỉ cần sử dụng một trong những mô-đun này.

Nó sẽ biến điều này:

dbGet('userIdOf:bobvance', function(userId) {
    dbSet('user:' + userId + ':email', 'bobvance@potato.egg', function() {
        dbSet('user:' + userId + ':firstName', 'Bob', function() {
            dbSet('user:' + userId + ':lastName', 'Vance', function() {
                okWeAreDone();
            });
        });
    });
});

Vào đây:

flow.exec(
    function() {
        dbGet('userIdOf:bobvance', this);

    },function(userId) {
        dbSet('user:' + userId + ':email', 'bobvance@potato.egg', this.MULTI());
        dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI());
        dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI());

    },function() {
        okWeAreDone()
    }
);

9
Có một cái nhìn nhanh về Flow-js, step và async và dường như chúng chỉ xử lý thứ tự thực hiện chức năng. Trong trường hợp của tôi có quyền truy cập vào các biến đóng cửa nội tuyến trong mỗi lần thụt lề. Vì vậy, ví dụ các chức năng hoạt động như thế này: lấy HTTP req / res, lấy userid từ DB cho cookie, nhận email cho userid sau, lấy thêm dữ liệu cho email sau, ..., lấy X cho Y sau, ... Nếu tôi không nhầm, các khung này chỉ đảm bảo rằng các hàm async sẽ được thực hiện theo đúng thứ tự, nhưng trong mọi thân hàm không có cách nào để có được biến được cung cấp một cách tự nhiên bởi các bao đóng (?) Cảm ơn :)
Kay Pale

9
Về việc xếp hạng các thư viện này, tôi đã kiểm tra số lượng "Sao" trên mỗi thư viện tại Github. async có nhiều nhất với khoảng 3000, Bước tiếp theo là khoảng 1000, các bước khác ít hơn đáng kể. Tất nhiên, tất cả họ đều không làm điều tương tự :-)
kgilpin

3
@KayPale Tôi có xu hướng sử dụng async.waterfall và đôi khi sẽ có các chức năng của riêng mình cho từng giai đoạn / bước sẽ vượt qua những gì bước tiếp theo cần, hoặc xác định các biến trước khi gọi async.METHOD để tuyến dưới có sẵn. Đồng thời sẽ sử dụng METHODNAME.bind (...) cho các cuộc gọi async. * Của tôi, cũng hoạt động khá tốt.
Tracker1

Một câu hỏi nhanh: Trong danh sách các mô-đun của bạn, hai mô-đun cuối cùng có giống nhau không? Tức là "async.js" và "async"
dari0h

18

Phần lớn, tôi đồng ý với Daniel Vassallo. Nếu bạn có thể chia một hàm phức tạp và được lồng sâu vào các hàm được đặt tên riêng biệt, thì đó thường là một ý tưởng tốt. Đối với những lần có ý nghĩa để thực hiện nó trong một hàm duy nhất, bạn có thể sử dụng một trong nhiều thư viện async của node.js có sẵn. Mọi người đã đưa ra rất nhiều cách khác nhau để giải quyết vấn đề này, vì vậy hãy xem trang mô-đun node.js và xem bạn nghĩ gì.

Tôi đã tự viết một mô-đun cho cái này, được gọi là async.js . Sử dụng điều này, ví dụ trên có thể được cập nhật thành:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  async.series({
    someData: async.apply(getSomeDate, client),
    someOtherData: async.apply(getSomeOtherDate, client),
    moreData: async.apply(getMoreData, client)
  },
  function (err, results) {
    var html = "<h1>Demo page</h1>";
    html += "<p>" + results.someData + "</p>";
    html += "<p>" + results.someOtherData + "</p>";
    html += "<p>" + results.moreData + "</p>";
    res.write(html);
    res.end();
  });
});

Một điều thú vị về cách tiếp cận này là bạn có thể nhanh chóng thay đổi mã của mình để tìm nạp dữ liệu song song bằng cách thay đổi chức năng 'chuỗi' thành 'song song'. Hơn nữa, async.js cũng sẽ hoạt động bên trong trình duyệt, vì vậy bạn có thể sử dụng các phương thức tương tự như trong node.js nếu bạn gặp phải bất kỳ mã async phức tạp nào.

Hy vọng điều đó hữu ích!


Xin chào Caolan và cảm ơn câu trả lời! Trong trường hợp của tôi có quyền truy cập vào các biến đóng cửa nội tuyến trong mỗi lần thụt lề. Vì vậy, ví dụ các chức năng hoạt động như thế này: lấy HTTP req / res, lấy userid từ DB cho cookie, nhận email cho userid sau, lấy thêm dữ liệu cho email sau, ..., lấy X cho Y sau, ... Nếu tôi không nhầm, mã bạn đề xuất chỉ đảm bảo rằng các hàm async sẽ được thực thi theo đúng thứ tự, nhưng trong mọi thân hàm không có cách nào để lấy biến được cung cấp một cách tự nhiên bởi các bao đóng trong mã gốc của tôi. Có phải vậy không?
Kay Pale

3
Những gì bạn đang cố gắng để đạt được về mặt kiến ​​trúc được gọi là một đường ống dữ liệu. Bạn có thể sử dụng thác async cho các trường hợp như vậy.
Rudolf Meijering

18

Bạn có thể sử dụng thủ thuật này với một mảng thay vì các hàm lồng nhau hoặc một mô-đun.

Dễ dàng hơn trên mắt.

var fs = require("fs");
var chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step3");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step4");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step5");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("done");
    },
];
chain.shift()();

Bạn có thể mở rộng thành ngữ cho các quy trình song song hoặc thậm chí các chuỗi quy trình song song:

var fs = require("fs");
var fork1 = 2, fork2 = 2, chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        var next = chain.shift();
        fs.stat("f2a.js",next);
        fs.stat("f2b.js",next);
    },
    function(err, stats) {
        if ( --fork1 )
            return;
        console.log("step3");
        var next = chain.shift();

        var chain1 = [
            function() { 
                console.log("step4aa");
                fs.stat("f1.js",chain1.shift());
            },
            function(err, stats) { 
                console.log("step4ab");
                fs.stat("f1ab.js",next);
            },
        ];
        chain1.shift()();

        var chain2 = [
            function() { 
                console.log("step4ba");
                fs.stat("f1.js",chain2.shift());
            },
            function(err, stats) { 
                console.log("step4bb");
                fs.stat("f1ab.js",next);
            },
        ];
        chain2.shift()();
    },
    function(err, stats) {
        if ( --fork2 )
            return;
        console.log("done");
    },
];
chain.shift()();

15

Tôi thích async.js rất nhiều cho mục đích này.

Vấn đề được giải quyết bằng lệnh thác nước:

thác nước (nhiệm vụ, [gọi lại])

Chạy một mảng các hàm theo chuỗi, mỗi chuỗi chuyển kết quả của chúng sang mảng tiếp theo trong mảng. Tuy nhiên, nếu bất kỳ chức năng nào chuyển lỗi đến cuộc gọi lại, chức năng tiếp theo sẽ không được thực thi và cuộc gọi lại chính ngay lập tức được gọi với lỗi.

Tranh luận

tác vụ - Một mảng các hàm để chạy, mỗi hàm được truyền lại một hàm gọi lại (err, result1, result2, ...) nó phải gọi khi hoàn thành. Đối số đầu tiên là một lỗi (có thể là null) và mọi đối số tiếp theo sẽ được chuyển dưới dạng đối số để thực hiện tác vụ tiếp theo. gọi lại (err, [results]) - Một cuộc gọi lại tùy chọn để chạy khi tất cả các chức năng đã hoàn thành. Điều này sẽ được thông qua kết quả của cuộc gọi lại của nhiệm vụ cuối cùng.

Thí dụ

async.waterfall([
    function(callback){
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        callback(null, 'three');
    },
    function(arg1, callback){
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'    
});

Đối với các biến req, res, chúng sẽ được chia sẻ trong cùng phạm vi với hàm (req, res) {} bao gồm toàn bộ lệnh gọi async.waterfall.

Không chỉ vậy, async rất sạch sẽ. Ý tôi là tôi thay đổi rất nhiều trường hợp như thế này:

function(o,cb){
    function2(o,function(err, resp){
        cb(err,resp);
    })
}

Trước tiên:

function(o,cb){
    function2(o,cb);
}

Sau đó đến đây:

function2(o,cb);

Sau đó đến đây:

async.waterfall([function2,function3,function4],optionalcb)

Nó cũng cho phép nhiều hàm tiền tố được chuẩn bị cho async được gọi từ produc.js rất nhanh. Chỉ cần xâu chuỗi những gì bạn muốn làm, đảm bảo o, cb được xử lý chung. Điều này tăng tốc toàn bộ quá trình mã hóa rất nhiều.


11

Những gì bạn cần là một chút đường cú pháp. Chek này ra:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = ["<h1>Demo page</h1>"];
  var pushHTML = html.push.bind(html);

  Queue.push( getSomeData.partial(client, pushHTML) );
  Queue.push( getSomeOtherData.partial(client, pushHTML) );
  Queue.push( getMoreData.partial(client, pushHTML) );
  Queue.push( function() {
    res.write(html.join(''));
    res.end();
  });
  Queue.execute();
}); 

Khá gọn gàng phải không? Bạn có thể nhận thấy rằng html đã trở thành một mảng. Điều đó một phần vì các chuỗi là bất biến, vì vậy bạn tốt hơn với việc đệm đầu ra của bạn trong một mảng, hơn là loại bỏ các chuỗi lớn hơn và lớn hơn. Lý do khác là vì một cú pháp tốt đẹp với bind.

Queuetrong ví dụ thực sự chỉ là một ví dụ và cùng với partialcó thể được thực hiện như sau

// Functional programming for the rescue
Function.prototype.partial = function() {
  var fun = this,
      preArgs = Array.prototype.slice.call(arguments);
  return function() {
    fun.apply(null, preArgs.concat.apply(preArgs, arguments));
  };
};

Queue = [];
Queue.execute = function () {
  if (Queue.length) {
    Queue.shift()(Queue.execute);
  }
};

1
Queue.execute () sẽ đơn giản thực hiện lần lượt từng phần, mà không cần chờ kết quả từ các cuộc gọi không đồng bộ.
ngn

Tại chỗ, cảm ơn. Tôi đã cập nhật câu trả lời. Đây là một bài kiểm tra: jsbin.com/ebobo5/edit (với lastchức năng tùy chọn )
gblazex

Xin chào galambalazs và cảm ơn câu trả lời! Trong trường hợp của tôi có quyền truy cập vào các biến đóng cửa nội tuyến trong mỗi lần thụt lề. Vì vậy, ví dụ các chức năng hoạt động như thế này: lấy HTTP req / res, lấy userid từ DB cho cookie, nhận email cho userid sau, lấy thêm dữ liệu cho email sau, ..., lấy X cho Y sau, ... Nếu tôi không nhầm, mã bạn đề xuất chỉ đảm bảo rằng các hàm async sẽ được thực thi theo đúng thứ tự, nhưng trong mọi thân hàm không có cách nào để lấy biến được cung cấp một cách tự nhiên bởi các bao đóng trong mã gốc của tôi. Có phải vậy không?
Kay Pale

1
Vâng, bạn chắc chắn mất đóng cửa trong tất cả các câu trả lời. Những gì bạn có thể làm là tạo một đối tượng trong phạm vi toàn cầu cho dữ liệu được chia sẻ . Vì vậy, ví dụ chức năng đầu tiên của bạn thêm obj.emailvà chức năng tiếp theo của bạn sử dụng obj.emailsau đó xóa nó (hoặc chỉ gán null).
gblazex

7

Tôi yêu Async.js kể từ khi tôi tìm thấy nó. Nó có một async.serieschức năng bạn có thể sử dụng để tránh làm tổ dài.

Tài liệu:-


chuỗi (nhiệm vụ, [gọi lại])

Chạy một loạt các hàm theo chuỗi, mỗi hàm chạy một khi chức năng trước đó đã hoàn thành. [...]

Tranh luận

tasks- Một mảng các chức năng để chạy, mỗi chức năng được thông qua một cuộc gọi lại nó phải gọi khi hoàn thành. callback(err, [results])- Một cuộc gọi lại tùy chọn để chạy khi tất cả các chức năng đã hoàn thành. Hàm này nhận được một mảng của tất cả các đối số được truyền cho các hàm gọi lại được sử dụng trong mảng.


Đây là cách chúng tôi có thể áp dụng nó cho mã ví dụ của bạn: -

http.createServer(function (req, res) {

    res.writeHead(200, {'Content-Type': 'text/html'});

    var html = "<h1>Demo page</h1>";

    async.series([
        function (callback) {
            getSomeData(client, function (someData) { 
                html += "<p>"+ someData +"</p>";

                callback();
            });
        },

        function (callback) {
            getSomeOtherData(client, function (someOtherData) { 
                html += "<p>"+ someOtherData +"</p>";

                callback(); 
            });
        },

        funciton (callback) {
            getMoreData(client, function (moreData) {
                html += "<p>"+ moreData +"</p>";

                callback();
            });
        }
    ], function () {
        res.write(html);
        res.end();
    });
});

6

Đường cú pháp đơn giản nhất tôi đã thấy là nút-lời hứa.

npm cài đặt lời hứa nút | | git clone https://github.com/kriszyp/node-promise

Sử dụng điều này, bạn có thể xâu chuỗi các phương thức async như:

firstMethod().then(secondMethod).then(thirdMethod);

Giá trị trả về của mỗi có sẵn như là đối số trong tiếp theo.


3

Những gì bạn đã làm ở đó là lấy một mẫu asynch và áp dụng nó cho 3 hàm được gọi theo thứ tự, mỗi hàm chờ cho cái trước hoàn thành trước khi bắt đầu - tức là bạn đã làm cho chúng đồng bộ . Điểm quan trọng về lập trình asynch là bạn có thể có một số chức năng tất cả chạy cùng một lúc và không phải chờ đợi để hoàn thành mỗi chức năng.

nếu getSomeDate () không cung cấp bất cứ điều gì cho getSomeOtherDate (), điều này không cung cấp bất cứ điều gì cho getMoreData () thì tại sao bạn không gọi chúng không đồng bộ vì js cho phép hoặc nếu chúng phụ thuộc lẫn nhau (và không đồng bộ) hãy viết chúng là một Chức năng đơn?

Bạn không cần sử dụng lồng nhau để kiểm soát luồng - ví dụ: làm cho mỗi hàm hoàn thành bằng cách gọi một hàm chung xác định khi cả 3 đã hoàn thành và sau đó gửi phản hồi.


2

Giả sử bạn có thể làm điều này:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    var html = "<h1>Demo page</h1>";
    chain([
        function (next) {
            getSomeDate(client, next);
        },
        function (next, someData) {
            html += "<p>"+ someData +"</p>";
            getSomeOtherDate(client, next);
        },
        function (next, someOtherData) {
            html += "<p>"+ someOtherData +"</p>";
            getMoreData(client, next);
        },
        function (next, moreData) {
            html += "<p>"+ moreData +"</p>";
            res.write(html);
            res.end();
        }
    ]);
});

Bạn chỉ cần triển khai chuỗi () để nó áp dụng một phần cho từng hàm cho hàm tiếp theo và ngay lập tức chỉ gọi hàm đầu tiên:

function chain(fs) {
    var f = function () {};
    for (var i = fs.length - 1; i >= 0; i--) {
        f = fs[i].partial(f);
    }
    f();
}

Chào ngn và cảm ơn câu trả lời! Trong trường hợp của tôi có quyền truy cập vào các biến đóng cửa nội tuyến trong mỗi lần thụt lề. Vì vậy, ví dụ các chức năng hoạt động như thế này: lấy HTTP req / res, lấy userid từ DB cho cookie, nhận email cho userid sau, lấy thêm dữ liệu cho email sau, ..., lấy X cho Y sau, ... Nếu tôi không nhầm, mã bạn đề xuất chỉ đảm bảo rằng các hàm async sẽ được thực thi theo đúng thứ tự, nhưng trong mọi thân hàm không có cách nào để lấy biến được cung cấp một cách tự nhiên bởi các bao đóng trong mã gốc của tôi. Có phải vậy không?
Kay Pale

2

gọi lại địa ngục có thể dễ dàng tránh được trong javascript thuần với đóng cửa. giải pháp bên dưới giả định tất cả các cuộc gọi lại tuân theo chữ ký hàm (lỗi, dữ liệu).

http.createServer(function (req, res) {
  var modeNext, onNext;

  // closure variable to keep track of next-callback-state
  modeNext = 0;

  // next-callback-handler
  onNext = function (error, data) {
    if (error) {
      modeNext = Infinity;
    } else {
      modeNext += 1;
    }
    switch (modeNext) {

    case 0:
      res.writeHead(200, {'Content-Type': 'text/html'});
      var html = "<h1>Demo page</h1>";
      getSomeDate(client, onNext);
      break;

    // handle someData
    case 1:
        html += "<p>"+ data +"</p>";
        getSomeOtherDate(client, onNext);
        break;

    // handle someOtherData
    case 2:
      html += "<p>"+ data +"</p>";
      getMoreData(client, onNext);
      break;

    // handle moreData
    case 3:
      html += "<p>"+ data +"</p>";
      res.write(html);
      res.end();
      break;

    // general catch-all error-handler
    default:
      res.statusCode = 500;
      res.end(error.message + '\n' + error.stack);
    }
  };
  onNext();
});

1

Gần đây tôi đã tạo ra sự trừu tượng đơn giản hơn được gọi là Wait.cho gọi các hàm async trong chế độ đồng bộ hóa (dựa trên Sợi). Đó là ở giai đoạn đầu nhưng hoạt động. Đó là tại:

https://github.com/luciotato/waitfor

Sử dụng Wait.for , bạn có thể gọi bất kỳ chức năng async nodejs tiêu chuẩn nào, như thể đó là một chức năng đồng bộ hóa.

sử dụng Wait.cho mã của bạn có thể là:

var http=require('http');
var wait=require('wait.for');

http.createServer(function(req, res) {
  wait.launchFiber(handleRequest,req, res); //run in a Fiber, keep node spinning
}).listen(8080);


//in a fiber
function handleRequest(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  var someData = wait.for(getSomeDate,client);
  html += "<p>"+ someData +"</p>";
  var someOtherData = wait.for(getSomeOtherDate,client);
  html += "<p>"+ someOtherData +"</p>";
  var moreData = wait.for(getMoreData,client);
  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
};

... hoặc nếu bạn muốn ít dài dòng hơn (và cũng thêm bắt lỗi)

//in a fiber
function handleRequest(req, res) {
  try {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(
    "<h1>Demo page</h1>" 
    + "<p>"+ wait.for(getSomeDate,client) +"</p>"
    + "<p>"+ wait.for(getSomeOtherDate,client) +"</p>"
    + "<p>"+ wait.for(getMoreData,client) +"</p>"
    );
    res.end();
  }
  catch(err) {
   res.end('error '+e.message); 
  }

};

Trong tất cả các trường hợp, getSomeDate , getSomeOtherDategetMoreData phải là các hàm async tiêu chuẩn với tham số cuối cùng là hàm gọi lại (err, data)

như trong:

function getMoreData(client, callback){
  db.execute('select moredata from thedata where client_id=?',[client.id],
       ,function(err,data){
          if (err) callback(err);
          callback (null,data);
        });
}

1

Để giải quyết vấn đề này, tôi đã viết gật đầu ( https://npmjs.org/package/nodent ) mà vô hình tiền xử lý JS của bạn. Mã ví dụ của bạn sẽ trở thành (không đồng bộ, thực sự - đọc tài liệu).

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  someData <<= getSomeDate(client) ;

  html += "<p>"+ someData +"</p>";
  someOtherData <<= getSomeOtherDate(client) ;

  html += "<p>"+ someOtherData +"</p>";
  moreData <<= getMoreData(client) ;

  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
});

Rõ ràng, có nhiều giải pháp khác, nhưng tiền xử lý có ưu điểm là có ít hoặc không có chi phí thời gian chạy và nhờ hỗ trợ bản đồ nguồn, nó cũng dễ dàng gỡ lỗi.


0

Tôi đã từng gặp vấn đề tương tự. Tôi đã thấy các lib chính cho nút chạy các hàm async và chúng thể hiện các chuỗi không tự nhiên (bạn cần sử dụng ba hoặc nhiều phương thức confs, v.v.) để xây dựng mã của mình.

Tôi đã dành vài tuần để phát triển một giải pháp đơn giản và dễ đọc. Xin vui lòng, hãy thử EnqJS . Tất cả các ý kiến ​​sẽ được đánh giá cao.

Thay vì:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

với EnqJS:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";

  enq(function(){
    var self=this;
    getSomeDate(client, function(someData){
      html += "<p>"+ someData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getSomeOtherDate(client, function(someOtherData){ 
      html += "<p>"+ someOtherData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getMoreData(client, function(moreData) {
      html += "<p>"+ moreData +"</p>";
      self.return();
      res.write(html);
      res.end();
    });
  });
});

Quan sát rằng mã dường như lớn hơn trước. Nhưng nó không được lồng như trước. Để xuất hiện tự nhiên hơn, các chuỗi được gọi là ngay lập tức:

enq(fn1)(fn2)(fn3)(fn4)(fn4)(...)

Và để nói rằng nó đã trả về, bên trong hàm chúng ta gọi:

this.return(response)

0

Tôi làm điều đó một cách khá nguyên thủy nhưng hiệu quả. Ví dụ: tôi cần có một mô hình với cha mẹ và con cái của nó và giả sử tôi cần thực hiện các truy vấn riêng cho chúng:

var getWithParents = function(id, next) {
  var getChildren = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      },
      getParents = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      }
      getModel = function(id, next) {
        /*... code ... */
        if (model) {
          // return next callbacl
          return next.pop()(model, next);
        } else {
          // return last callback
          return next.shift()(null, next);
        }
      }

  return getModel(id, [getParents, getChildren, next]);
}

0

Sử dụng sợi https://github.com/laverdet/node-fibers nó làm cho mã không đồng bộ trông giống như đồng bộ (không bị chặn)

Cá nhân tôi sử dụng trình bao bọc nhỏ này http://alexeypetrushin.github.com/synyncize Mẫu mã từ dự án của tôi (mọi phương thức thực sự không đồng bộ, làm việc với tệp async IO) Tôi thậm chí sợ tưởng tượng nó sẽ là một mớ hỗn độn với cuộc gọi lại hoặc thư viện trợ giúp async-control-Flow.

_update: (version, changesBasePath, changes, oldSite) ->
  @log 'updating...'
  @_updateIndex version, changes
  @_updateFiles version, changesBasePath, changes
  @_updateFilesIndexes version, changes
  configChanged = @_updateConfig version, changes
  @_updateModules version, changes, oldSite, configChanged
  @_saveIndex version
  @log "updated to #{version} version"

0

Task.js cung cấp cho bạn điều này:

spawn(function*() {
    try {
        var [foo, bar] = yield join(read("foo.json"),
                                    read("bar.json")).timeout(1000);
        render(foo);
        render(bar);
    } catch (e) {
        console.log("read failed: " + e);
    }
});

Thay vì điều này:

var foo, bar;
var tid = setTimeout(function() { failure(new Error("timed out")) }, 1000);

var xhr1 = makeXHR("foo.json",
                   function(txt) { foo = txt; success() },
                   function(err) { failure() });
var xhr2 = makeXHR("bar.json",
                   function(txt) { bar = txt; success() },
                   function(e) { failure(e) });

function success() {
    if (typeof foo === "string" && typeof bar === "string") {
        cancelTimeout(tid);
        xhr1 = xhr2 = null;
        render(foo);
        render(bar);
    }
}

function failure(e) {
    xhr1 && xhr1.abort();
    xhr1 = null;
    xhr2 && xhr2.abort();
    xhr2 = null;
    console.log("read failed: " + e);
}

0

Sau khi những người khác trả lời, bạn nói rằng vấn đề của bạn là các biến cục bộ. Có vẻ như một cách dễ dàng để làm điều này là viết một hàm ngoài để chứa các biến cục bộ đó, sau đó sử dụng một loạt các hàm bên trong có tên và truy cập chúng theo tên. Bằng cách này, bạn sẽ chỉ bao giờ lồng hai cái sâu, bất kể bạn cần bao nhiêu chức năng để liên kết với nhau.

Đây là nỗ lực của người mới của tôi trong việc sử dụng mysqlmô-đun Node.js với lồng nhau:

function with_connection(sql, bindings, cb) {
    pool.getConnection(function(err, conn) {
        if (err) {
            console.log("Error in with_connection (getConnection): " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, function(err, results) {
            if (err) {
                console.log("Error in with_connection (query): " + JSON.stringify(err));
                cb(true);
                return;
            }
            console.log("with_connection results: " + JSON.stringify(results));
            cb(false, results);
        });
    });
}

Sau đây là viết lại bằng cách sử dụng các chức năng bên trong được đặt tên. Hàm ngoài with_connectioncó thể được sử dụng như một bộ giữ cho các biến cục bộ. (Ở đây, tôi đã có các thông số sql, bindings, cbmà hành động theo một cách tương tự, nhưng bạn chỉ có thể xác định một số biến địa phương bổ sung trong with_connection.)

function with_connection(sql, bindings, cb) {

    function getConnectionCb(err, conn) {
        if (err) {
            console.log("Error in with_connection/getConnectionCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, queryCb);
    }

    function queryCb(err, results) {
        if (err) {
            console.log("Error in with_connection/queryCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        cb(false, results);
    }

    pool.getConnection(getConnectionCb);
}

Tôi đã nghĩ rằng có lẽ có thể tạo ra một đối tượng với các biến thể hiện và sử dụng các biến thể hiện này để thay thế cho các biến cục bộ. Nhưng bây giờ tôi thấy rằng cách tiếp cận trên sử dụng các hàm lồng nhau và các biến cục bộ đơn giản và dễ hiểu hơn. Phải mất một thời gian để học OO, có vẻ như :-)

Vì vậy, đây là phiên bản trước của tôi với một biến đối tượng và thể hiện.

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, function(err, results) { self.query(err, results); });
}
DbConnection.prototype.query = function(err, results) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        self.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    self.cb(false, results);
}

function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    pool.getConnection(function (err, conn) { dbc.getConnection(err, conn); });
}

Nó chỉ ra rằng bindcó thể được sử dụng để một số lợi thế. Nó cho phép tôi loại bỏ các hàm ẩn danh hơi xấu xí mà tôi đã tạo mà không làm được gì nhiều, ngoại trừ tự chuyển tiếp đến một cuộc gọi phương thức. Tôi không thể truyền trực tiếp phương thức này vì nó có liên quan đến giá trị sai của this. Nhưng với bind, tôi có thể chỉ định giá trị thismà tôi muốn.

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var f = this.query.bind(this);
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, f);
}
DbConnection.prototype.query = function(err, results) {
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    this.cb(false, results);
}

// Get a connection from the pool, execute `sql` in it
// with the given `bindings`.  Invoke `cb(true)` on error,
// invoke `cb(false, results)` on success.  Here,
// `results` is an array of results from the query.
function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    var f = dbc.getConnection.bind(dbc);
    pool.getConnection(f);
}

Tất nhiên, không có cái nào trong số này là JS phù hợp với mã hóa Node.js - tôi chỉ dành vài giờ cho nó. Nhưng có lẽ với một chút đánh bóng kỹ thuật này có thể giúp đỡ?





0

Sử dụng dây mã của bạn sẽ trông như thế này:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});

    var l = new Wire();

    getSomeDate(client, l.branch('someData'));
    getSomeOtherDate(client, l.branch('someOtherData'));
    getMoreData(client, l.branch('moreData'));

    l.success(function(r) {
        res.write("<h1>Demo page</h1>"+
            "<p>"+ r['someData'] +"</p>"+
            "<p>"+ r['someOtherData'] +"</p>"+
            "<p>"+ r['moreData'] +"</p>");
        res.end();
    });
});

0

để bạn biết, hãy xem xét Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase

    const jj = Yêu cầu ('jazz.js');

    // ngăn xếp siêu compat
    jj.script ([
        a => ProcessTaskOneCallbackAtEnd (a),
        b => ProcessTaskTwoCallbackAtEnd (b),
        c => ProcessTaskThreeCallbackAtEnd (c),
        d => ProcessTaskFourCallbackAtEnd (d),
        e => ProcessTaskFiveCallbackAtEnd (e),
    ]);

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.