2011-12-20 3 views
6

Tôi mới sử dụng Node và cố gắng đảm bảo rằng tôi đang sử dụng thiết kế sane cho ứng dụng web có định dạng JSON.Mẫu tốt nhất để xử lý vòng lặp không đồng bộ trong Node.js

Tôi đã có một loạt dữ liệu được lưu trữ trong Redis và đang truy xuất dữ liệu qua nút, truyền trực tuyến kết quả khi chúng đến từ Redis. Dưới đây là một ví dụ tốt về những gì tôi đang làm:

app.get("/facility", function(req, res) { 
    rc.keys("FACILITY*", function(err, replies) { 
     res.write("["); 
     replies.forEach(function (reply, i) { 
      rc.get(reply, function(err, reply) { 
       res.write(reply); 
       if (i == replies.length-1) { 
        res.write("]"); 
        res.end(); 
       } 
       else 
        res.write(","); 
      }); 
     }); 
    }); 
}); 

Về cơ bản tôi nhận được một tập hợp các phím từ Redis và sau đó yêu cầu mỗi người, streaming ra kết quả vào bán bằng tay tạo ra JSON (sợi dây ra khỏi Redis đã có trong JSON). Bây giờ điều này làm việc độc đáo, nhưng tôi không thể không nghĩ rằng i == reply.length-1 là một chút lộn xộn?

Tôi có thể làm tất cả điều này với mget trong Redis, nhưng đó không thực sự là điểm tôi đang cố gắng để có được nó; đó là cách tốt nhất để xử lý vòng lặp async với forEach, phát trực tuyến đầu ra và kết thúc một cách duyên dáng khỏi kết nối với res.end với vòng lặp được thực hiện.

Đây có phải là cách tốt nhất hoặc có mẫu trang nhã hơn tôi có thể theo dõi không?

+0

đối với vùng sâu callbacks hàm lồng nhau tôi sẽ sử dụng async thư viện .js. – BRampersad

Trả lời

6

Mã trên có thể không làm những gì bạn mong đợi. Bạn đang khởi động từng chuỗi .get() theo thứ tự, nhưng chúng có thể không gọi lại theo thứ tự - do đó, kết quả có thể phát trực tuyến theo bất kỳ thứ tự nào. Nếu bạn muốn truyền các kết quả thay vì thu thập chúng trong bộ nhớ, bạn cần phải theo thứ tự .get().

Tôi nghĩ rằng caolan’s async library làm cho việc này trở nên dễ dàng hơn. Dưới đây là một cách để bạn có thể sử dụng nó để có được từng hạng mục theo thứ tự (cảnh báo, chưa được kiểm tra):

app.get("/facility", function(req, res) { 
    rc.keys("FACILITY*", function(err, replies) { 
     var i = 0; 
     res.write("["); 
     async.forEachSeries(replies, function(reply, callback){ 
      rc.get(reply, function(err, reply) { 
       if (err){ 
        callback(err); 
        return; 
       } 
       res.write(reply); 
       if (i < replies.length) { 
        res.write(","); 
       } 
       i++; 
       callback(); 
      }); 
     }, function(err){ 
      if (err) { 
       // Handle an error 
      } else { 
       res.end(']'); 
      } 
     }); 
    }); 
}); 

Nếu bạn không quan tâm đến thứ tự, chỉ cần sử dụng async.forEach() để thay thế.

Nếu bạn không quan tâm thu thập các kết quả và muốn họ quay trở lại theo thứ tự, bạn có thể sử dụng async.map() như thế này (cảnh báo, cũng chưa được kiểm tra):

app.get("/facility", function(req, res) { 
    rc.keys("FACILITY*", function(err, replies) { 
     async.map(replies, rc.get.bind(rc), function(err, replies){ 
      if (err) { 
       // Handle an error 
      } else { 
       res.end('[' + replies.join(',') + ']'); 
      } 
     }); 
    }); 
}); 
+0

Thật tuyệt vời; cảm ơn mã; Tôi đã thử thư viện async và nó hoạt động hoàn hảo. Thứ tự không quan trọng, nhưng giải pháp bản đồ trông thanh lịch hơn rất nhiều, vì vậy tôi có thể chỉ dính vào đó. –

+0

Tôi đang cố gắng tìm hiểu cách tham số rc.get.bind (rc) trong chức năng gọi bản đồ hoạt động; nó là một cách tiện lợi để làm điều đó. Bạn có thể giải thích một chút về cách hoạt động chính xác? –

+0

@mjs [bind] (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind) là một phần của ECMAScript 5, và nó trả về một bản sao của một hàm được "liên kết" với một giá trị 'this' cụ thể. Trong trường hợp này, có nghĩa là khi async.map gọi 'get()', nó sẽ có 'rc' làm giá trị' this' của nó. – s4y

3

Bạn có thể sử dụng thư viện async, nó cung cấp một số phương pháp tiện dụng cho vòng lặp, như foreach:

foreach (arr, iterator, callback)

Áp dụng một chức năng lặp cho mỗi mục trong một mảng, song song. Trình lặp được gọi với một mục từ danh sách và gọi lại cho khi kết thúc. Nếu trình vòng lặp chuyển lỗi tới số gọi lại này, cuộc gọi lại chính cho hàm forEach ngay lập tức là được gọi với lỗi. Lưu ý rằng vì chức năng này áp dụng trình lặp cho từng mục trong song song không có gì đảm bảo rằng các chức năng của trình lặp sẽ hoàn thành theo thứ tự.

Ví dụ

// assuming openFiles is an array of file names and saveFile is a function 
// to save the modified contents of that file: 

async.forEach(openFiles, saveFile, function(err){ 
    // if any of the saves produced an error, err would equal that error 
}); 
+0

Tôi sẽ xem thư viện đó; cảm ơn cho con trỏ. –

1

nhưng tôi không thể không nghĩ rằng tôi == replies.length-1 là một chút lộn xộn?

Tôi đã nghe rất nhiều người nói vậy. Đây là cách tôi sẽ làm điều đó bằng tay:

app.get("/facility", function(req, res, next) { 
    rc.keys("FACILITY*", function(err, replies) { 
    if (err) return next(err); 
    var pending = replies.length; 
    res.write("["); 
    replies.forEach(function (reply) { 
     rc.get(reply, function(err, reply) { 
     res.write(reply); 
     if (!--pending) { 
      res.write("]"); 
      return res.end(); 
     } 
     res.write(","); 
     }); 
    }); 
    }); 
}); 

Rõ ràng làm việc đó bằng tay là không quá xinh đẹp, đó là lý do tại sao mọi người có nó trừu tượng thành một thư viện hay một số chức năng khác. Nhưng cũng giống như nó hay không, đó là cách bạn làm một vòng lặp song song async. :)

Bạn có thể sử dụng thư viện async được đề cập trước để ẩn nội dung khó chịu.

+0

Đó là một cách neater để làm điều đó; cảm ơn vì điều đó. –