2014-04-06 18 views
9

Tôi đang cố gắng tìm hiểu một chút về lập trình Node và không đồng bộ. Tôi đọc về Lời hứa và đã cố gắng sử dụng chúng trong một dự án nhỏ sao chép bài đăng cho người dùng từ Dịch vụ A đến Dịch vụ B. Tôi gặp một số khó khăn khi hiểu cách tốt nhất để vượt qua trạng thái giữa Lời hứaCó những mẫu gì để vượt qua trạng thái thông qua một chuỗi các lời hứa trong Javascript?

Dự án được viết cho NodeJS sử dụng Promise library

một định nghĩa đơn giản của vấn đề hiện tại của tôi là:

  • sao chép bài viết cho một người sử dụng từ dịch vụ từ A đến B Dịch vụ nếu bài viết chưa tồn tại trong dịch vụ B.
  • Cả hai dịch vụ đều cung cấp các API http yêu cầu không phải id người dùng đáng nhớ để tra cứu bài đăng cho người dùng đó để id người dùng phải được tra cứu từ tên người dùng.
  • Tất cả các cuộc gọi http đều không đồng bộ.

Đây là một số mã giả minh họa cách tôi đã ghép các Lời hứa với nhau.

Promise.from('service_A_username') 
    .then(getServiceAUserIdForUsername) 
    .then(getServiceAPostsForUserId) 
    .then(function(serviceAPosts) { 
    // but what? store globally for access later? 
    doSomethingWith(serviceAPosts); 
    return Promise.from('service_B_username'); 
    }) 
    .then(getServiceBUserIdForUsername) 
    .then(getServiceBPostsForUserId) 
    .done(function(serviceBPosts) { 
    // how do we interact with Service A posts? 
    doSomethingThatInvolvesServiceAPostsWith(serviceBPosts); 
    }); 

Có một vài điều mà tôi đã nghĩ đến việc thực hiện:

  1. Mang cuộc gọi getIdForUsername bên trong hàm getPostsForUserId. Tuy nhiên, tôi muốn giữ cho từng đơn vị chức năng đơn giản nhất có thể theo nguyên tắc 'làm một việc và làm tốt'.
  2. Tạo đối tượng 'bối cảnh' và chuyển nó qua toàn bộ chuỗi, đọc và lưu trữ trạng thái trong đối tượng này. Tuy nhiên cách tiếp cận này làm cho mỗi chức năng rất riêng biệt cho một chuỗi và do đó khó sử dụng trong sự cô lập.

Có bất kỳ tùy chọn nào khác và cách tiếp cận nào được đề xuất không?

+1

FWIW, tùy chọn đối tượng bối cảnh không phải là tất cả * mà * xấu. Nếu bạn nghĩ của 'getServiceAUserIdForUsername' chức năng như "chấp nhận một 'lập luận userName' và trả về một' userId'", sau đó "chấp nhận một đối tượng với một 'tài sản userName' và điền vào một' tài sản userId'" không * rằng * cụ thể hơn đối với trường hợp sử dụng. Chắc chắn có nhiều khớp nối hơn (gọi mã và chức năng cần phải đồng ý về những tên đó, mà thông thường chúng sẽ không), nhưng thực tế là nếu bạn muốn nhiều hơn "chỉ một kết quả arg trong một giá trị trả về", bạn sẽ cần để có một số cách xác định các bit khác nhau. –

+0

Nhưng nếu bạn muốn, tôi có thể thấy có một cơ sở hạ tầng tập hợp chung chung để chỉ mã gọi điện thoại mới biết những tên thuộc tính đó. Cơ sở hạ tầng aggregator sẽ chấp nhận một danh sách "một sản lượng arg một kết quả" chức năng của bạn Promise-enabled (vì vậy không có tên được biết đến với những chức năng) và tên sử dụng, và quấn các cuộc gọi cho họ để điền vào các đối tượng. Hừm. Tôi tự hỏi nó sẽ như thế nào. –

+0

Cảm ơn @ t-j-crowder Đối với một đối tượng ngữ cảnh đang được chuyển qua một đường ống, bạn có biết về bất kỳ thư viện JS nào sử dụng mẫu này một cách cụ thể không? Java có [Apache Camel] (http://camel.apache.org/) được đặc biệt hướng đến loại định tuyến đó và tôi tự hỏi liệu có điều gì tương tự với js hay không. –

Trả lời

3

Tôi sẽ sử dụng Promise.all, như thế này

Promise.all([Promise.from('usernameA'), Promise.from('usernameB')]) 
    .then(function(result) { 
     return Promise.all([getUsername(result[0]),getUsername(result[1])]) 
    }) 
    .then(function(result) { 
     return Promise.all([getPosts(result[0]),getPosts(result[1])]); 
    }) 
    .then(function(result) { 
     var postsA = result[0], postsB = result[1]; 
     // Work with both the posts here 
    }); 
+0

Tôi thích câu trả lời này @thefourtheye. Có hợp lý để giữ các chuỗi lời hứa trong mảng được chuyển đến Promise.all như sau: https://gist.github.com/andystanton/f9ba6135a523971b6775 Xin lỗi vì 50 lần đăng lại - Tôi không nhận ra bạn có thể ' t đặt mã trả lời cho câu trả lời! –

+1

@Andy Vâng, tôi phải nói, nó có vẻ tốt hơn so với giải pháp tôi đề xuất :) – thefourtheye

6

Trước hết câu hỏi hay. Đây là điều chúng tôi (ít nhất là tôi) đối phó với những lời hứa thường xuyên. Nó cũng là một nơi hứa hẹn thực sự tỏa sáng trên callbacks theo ý kiến ​​của tôi.

gì đang xảy ra ở đây về cơ bản là bạn thực sự muốn hai điều rằng thư viện của bạn không có:

  1. .spread mà phải mất một lời hứa trả về một mảng và thay đổi nó từ một tham số mảng các thông số. Điều này cho phép cắt những thứ như .then(result) { var postsA = result[0], postsB = result[1]; thành .spread(postsA,postsB.

  2. .map có một loạt các lời hứa và ánh xạ từng lời hứa trong một mảng cho một lời hứa khác - giống như .then nhưng đối với mỗi giá trị của một mảng.

Có hai lựa chọn, hoặc là sử dụng thực hiện điều đó đã sử dụng chúng như Bluebird mà tôi khuyên bạn vì nó là bao la vượt trội so với các lựa chọn thay thế ngay bây giờ (nhanh hơn, ngăn xếp dấu vết tốt hơn, hỗ trợ tốt hơn, mạnh hơn tính năng thiết lập) HOẶC bạn có thể thực hiện chúng.

Vì đây là một câu trả lời và không phải là một đề nghị thư viện, chúng ta hãy làm điều đó:

Hãy bắt đầu với lan, đây là tương đối dễ dàng - tất cả nó có nghĩa là đang kêu gọi Function#apply đó lây lan một mảng vào varargs. Đây là một thực hiện mẫu tôi stole from myself:

if (!Promise.prototype.spread) { 
    Promise.prototype.spread = function (fn) { 
     return this.then(function (args) { 
     //this is always undefined in A+ complaint, but just in case 
      return fn.apply(this, args); 
     }); 

    }; 
} 

Tiếp theo, chúng ta hãy làm bản đồ. .map lời hứa là về cơ bản chỉ là mảng lập bản đồ với một thì:

if(!Promise.prototype.map){ 
    Promise.prototype.map = function (mapper) { 
     return this.then(function(arr){ 
      mapping = arr.map(mapper); // map each value 
      return Promise.all(mapping); // wait for all mappings to complete 
     }); 
    } 
} 

Để thuận tiện, chúng ta có thể giới thiệu một đối tác tĩnh của .map để bắt đầu chuỗi:

Promise.map = function(arr,mapping){ 
    return Promise.resolve(arr).map(mapping); 
}; 

Bây giờ, chúng ta có thể viết mã của bạn như chúng tôi thực sự muốn:

var names = ["usernameA","usernameB"]; // can scale to arbitrarily long. 
Promise.map(names, getUsername).map(getPosts).spread(function(postsA,postsB){ 
    // work with postsA,postsB and whatever 
}); 

Cú pháp chúng tôi thực sự muốn là gì. Không có sự lặp lại mã, nó là DRY, súc tích và rõ ràng, vẻ đẹp của lời hứa. Lưu ý rằng điều này không làm xước bề mặt của Bluebird - ví dụ, Bluebird sẽ phát hiện đó là một chuỗi bản đồ và sẽ 'đẩy' các chức năng vào yêu cầu thứ hai mà không cần đầu tiên thậm chí kết thúc, do đó, getUsername cho người sử dụng đầu tiên sẽ không chờ đợi cho người dùng thứ hai nhưng thực sự sẽ gọi getPosts nếu đó là nhanh hơn, vì vậy trong trường hợp này đó là nhanh như phiên bản ý chính của riêng bạn trong khi rõ ràng hơn imo.

Tuy nhiên, nó đang hoạt động, và là tốt đẹp.

Barebones A + triển khai được nhiều hơn cho khả năng tương tác giữa các thư viện hứa hẹn và được coi là một 'đường cơ sở'. Chúng hữu ích khi thiết kế các API nhỏ nền tảng cụ thể - IMO hầu như không bao giờ. Một thư viện vững chắc như Bluebird có thể làm giảm đáng kể mã của bạn. Thư viện hứa hẹn bạn đang sử dụng, thậm chí nói trong tài liệu của họ:

Nó được thiết kế để có được điểm cơ bản chính xác, để bạn có thể xây dựng triển khai hứa hẹn mở rộng trên đầu trang của nó.

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