Làm cách nào để truy cập kết quả lời hứa trước đó trong chuỗi .then ()?


650

Tôi đã cấu trúc lại mã của mình theo các lời hứa và xây dựng chuỗi hứa hẹn phẳng dài tuyệt vời , bao gồm nhiều .then()cuộc gọi lại. Cuối cùng, tôi muốn trả về một số giá trị tổng hợp và cần truy cập vào nhiều kết quả hứa hẹn trung gian . Tuy nhiên, các giá trị độ phân giải từ giữa chuỗi không nằm trong phạm vi trong cuộc gọi lại cuối cùng, làm thế nào để tôi truy cập chúng?

function getExample() {
    return promiseA(…).then(function(resultA) {
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        return // How do I gain access to resultA here?
    });
}

2
Câu hỏi này thực sự thú vị và ngay cả khi nó được gắn thẻ javascript, nó có liên quan trong ngôn ngữ khác. Tôi chỉ sử dụng câu trả lời "phá vỡ chuỗi" trong java và jdeferred
gontard

Câu trả lời:


377

Phá vỡ dây chuyền

Khi bạn cần truy cập các giá trị trung gian trong chuỗi của mình, bạn nên tách chuỗi của mình thành các phần duy nhất mà bạn cần. Thay vì đính kèm một cuộc gọi lại và bằng cách nào đó cố gắng sử dụng tham số của nó nhiều lần, hãy đính kèm nhiều cuộc gọi lại vào cùng một lời hứa - bất cứ nơi nào bạn cần giá trị kết quả. Đừng quên, một lời hứa chỉ đại diện cho (proxy) một giá trị trong tương lai ! Bên cạnh việc thực hiện một lời hứa từ chuỗi khác trong chuỗi tuyến tính, hãy sử dụng các tổ hợp lời hứa được thư viện của bạn trao cho bạn để xây dựng giá trị kết quả.

Điều này sẽ dẫn đến một luồng điều khiển rất đơn giản, thành phần rõ ràng của các chức năng và do đó dễ dàng mô đun hóa.

function getExample() {
    var a = promiseA(…);
    var b = a.then(function(resultA) {
        // some processing
        return promiseB(…);
    });
    return Promise.all([a, b]).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Thay vì phá hủy tham số trong cuộc gọi lại sau Promise.allđó chỉ khả dụng với ES6, trong ES5, thencuộc gọi sẽ được thay thế bằng phương thức trợ giúp tiện lợi được cung cấp bởi nhiều thư viện hứa hẹn ( Q , Bluebird , khi , giật ) : .spread(function(resultA, resultB) { ….

Bluebird cũng có một joinchức năng chuyên dụng để thay thế sự kết hợp Promise.all+ đó spreadbằng cấu trúc đơn giản hơn (và hiệu quả hơn):


return Promise.join(a, b, function(resultA, resultB) {  });

1
Các hàm bên trong mảng được thực hiện theo thứ tự?
đáng sợ

6
@scaryguy: Không có chức năng nào trong mảng, đó là những lời hứa. promiseApromiseBlà các chức năng (hứa trả lại) ở đây.
Bergi

2
@Roland Chưa bao giờ nói đó là :-) Câu trả lời này được viết trong thời đại ES5, nơi không có lời hứa nào trong tiêu chuẩn, và cực kỳ spreadhữu ích trong mẫu này. Đối với các giải pháp hiện đại hơn xem câu trả lời được chấp nhận. Tuy nhiên, tôi đã cập nhật câu trả lời rõ ràng và thực sự không có lý do chính đáng nào để không cập nhật câu trả lời này.
Bergi

1
@reify Không, bạn không nên làm điều đó , nó sẽ mang lại rắc rối với sự từ chối.
Bergi

1
Tôi không hiểu ví dụ này. Nếu có một chuỗi các câu lệnh 'thì' yêu cầu các giá trị đó được truyền bá trong toàn chuỗi, tôi không thấy cách này giải quyết vấn đề. Một lời hứa yêu cầu giá trị trước đó KHÔNG THỂ được kích hoạt (tạo) cho đến khi giá trị đó xuất hiện. Hơn nữa, Promise.all () chỉ đơn giản là chờ tất cả các lời hứa trong danh sách của mình kết thúc: nó không áp đặt một đơn đặt hàng. Vì vậy, tôi cần mỗi chức năng 'tiếp theo' để có quyền truy cập vào tất cả các giá trị trước đó và tôi không thấy ví dụ của bạn làm điều đó như thế nào. Bạn nên dẫn chúng tôi qua ví dụ của bạn, bởi vì tôi không tin hoặc không hiểu nó.
David Spector

238

Hòa âm ECMAScript

Tất nhiên, vấn đề này đã được công nhận bởi các nhà thiết kế ngôn ngữ. Họ đã làm rất nhiều việc và đề xuất chức năng không đồng bộ cuối cùng đã được thực hiện

Bản thảo 8

Bạn không cần một thenchức năng gọi lại hoặc gọi lại nữa, như trong chức năng không đồng bộ (trả lại lời hứa khi được gọi), bạn có thể chỉ cần chờ đợi lời hứa được giải quyết trực tiếp. Nó cũng có các cấu trúc điều khiển tùy ý như điều kiện, vòng lặp và mệnh đề thử, nhưng để thuận tiện, chúng tôi không cần chúng ở đây:

async function getExample() {
    var resultA = await promiseA(…);
    // some processing
    var resultB = await promiseB(…);
    // more processing
    return // something using both resultA and resultB
}

Bản thảo 6

Trong khi chờ ES8, chúng tôi đã sử dụng một loại cú pháp rất giống nhau. ES6 đi kèm với các chức năng của trình tạo , cho phép phân tách thực thi thành từng phần tại các yieldtừ khóa được đặt tùy ý . Các lát cắt đó có thể được chạy theo nhau, độc lập, thậm chí không đồng bộ - và đó chỉ là những gì chúng ta làm khi chúng ta muốn chờ đợi một giải pháp hứa hẹn trước khi chạy bước tiếp theo.

Có các thư viện chuyên dụng (như co hoặc task.js ), nhưng cũng có nhiều thư viện hứa hẹn có các hàm trợ giúp ( Q , Bluebird , khi , Nott ) thực hiện việc thực hiện từng bước không đồng bộ này cho bạn khi bạn cung cấp cho chúng một hàm tạo mang lại những lời hứa.

var getExample = Promise.coroutine(function* () {
//               ^^^^^^^^^^^^^^^^^ Bluebird syntax
    var resultA = yield promiseA(…);
    // some processing
    var resultB = yield promiseB(…);
    // more processing
    return // something using both resultA and resultB
});

Điều này đã hoạt động trong Node.js kể từ phiên bản 4.0, cũng có một vài trình duyệt (hoặc phiên bản dev của chúng) đã hỗ trợ cú pháp trình tạo tương đối sớm.

Bản thảo 5

Tuy nhiên, nếu bạn muốn / cần tương thích ngược, bạn không thể sử dụng những thứ đó mà không có bộ chuyển đổi. Cả hai chức năng của trình tạo và các hàm không đồng bộ đều được hỗ trợ bởi công cụ hiện tại, xem ví dụ về tài liệu của Babel về các trình tạo và các hàm không đồng bộ .

Và sau đó, cũng có nhiều ngôn ngữ biên dịch-to-JS khác được dành riêng để giảm bớt lập trình không đồng bộ. Họ thường sử dụng một cú pháp tương tự await, (ví dụ Iced CoffeeScript ), nhưng cũng có những cú pháp khác có chú thích giống doHaskell (ví dụ: LatteJs , monadic , PureScript hoặc LispyScript ).


@Bergi bạn có cần chờ chức năng async kiểm tra getExample () từ mã bên ngoài không?
araluexis

@arisalexis: Có, getExamplevẫn là một hàm trả về một lời hứa, hoạt động giống như các hàm trong các câu trả lời khác, nhưng với cú pháp đẹp hơn. Bạn có thể awaitgọi trong một asyncchức năng khác , hoặc bạn có thể xâu chuỗi .then()kết quả của nó.
Bergi

1
Tôi tò mò, tại sao bạn lại trả lời câu hỏi của riêng bạn ngay sau khi hỏi nó? Có một số cuộc thảo luận tốt ở đây, nhưng tôi tò mò. Có lẽ bạn đã tự tìm thấy câu trả lời của mình sau khi hỏi?
granmoe

@granmoe: Tôi đã đăng toàn bộ cuộc thảo luận về mục đích như một mục tiêu trùng lặp chính tắc
Bergi

Có cách nào (không quá tốn công) để tránh sử dụng Promise.coroutine (nghĩa là không sử dụng Bluebird hoặc thư viện khác mà chỉ sử dụng JS đơn giản) trong ví dụ ECMAScript 6 với hàm tạo? Tôi đã nghĩ đến một cái gì đó giống như steps.next().value.then(steps.next)...nhưng điều đó đã không làm việc.
một người học không có tên

102

Kiểm tra đồng bộ

Chỉ định các giá trị hứa hẹn cho sau này cần thiết cho các biến và sau đó nhận giá trị của chúng thông qua kiểm tra đồng bộ. Ví dụ sử dụng .value()phương thức của bluebird nhưng nhiều thư viện cung cấp phương thức tương tự.

function getExample() {
    var a = promiseA(…);

    return a.then(function() {
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // a is guaranteed to be fulfilled here so we can just retrieve its
        // value synchronously
        var aValue = a.value();
    });
}

Điều này có thể được sử dụng cho nhiều giá trị như bạn muốn:

function getExample() {
    var a = promiseA(…);

    var b = a.then(function() {
        return promiseB(…)
    });

    var c = b.then(function() {
        return promiseC(…);
    });

    var d = c.then(function() {
        return promiseD(…);
    });

    return d.then(function() {
        return a.value() + b.value() + c.value() + d.value();
    });
}

6
Đây là câu trả lời yêu thích của tôi: sự phụ thuộc dễ đọc, có thể mở rộng và tối thiểu vào các tính năng của thư viện hoặc ngôn ngữ
Jason

13
@Jason: Uh, " sự phụ thuộc tối thiểu vào các tính năng của thư viện "? Kiểm tra đồng bộ là một tính năng của thư viện và là một tính năng không chuẩn để khởi động.
Bergi

2
Tôi nghĩ rằng anh ta có nghĩa là các tính năng cụ thể của thư viện
deathgaze

54

Nesting (và) đóng cửa

Sử dụng các bao đóng để duy trì phạm vi của các biến (trong trường hợp của chúng tôi, các tham số hàm gọi lại thành công) là giải pháp JavaScript tự nhiên. Với lời hứa, chúng ta có thể tùy ý lồng và làm phẳng các .then() cuộc gọi lại - chúng tương đương về mặt ngữ nghĩa, ngoại trừ phạm vi của cuộc gọi bên trong.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(function(resultB) {
            // more processing
            return // something using both resultA and resultB;
        });
    });
}

Tất nhiên, đây là xây dựng một kim tự tháp thụt. Nếu độ thụt quá lớn, bạn vẫn có thể áp dụng các công cụ cũ để chống lại kim tự tháp của sự diệt vong : mô đun hóa, sử dụng các hàm có tên bổ sung và làm phẳng chuỗi hứa hẹn ngay khi bạn không cần biến nữa.
Về lý thuyết, bạn luôn có thể tránh nhiều hơn hai cấp độ lồng nhau (bằng cách làm cho tất cả các bao đóng rõ ràng), trong thực tế sử dụng càng nhiều càng hợp lý.

function getExample() {
    // preprocessing
    return promiseA(…).then(makeAhandler(…));
}
function makeAhandler(…)
    return function(resultA) {
        // some processing
        return promiseB(…).then(makeBhandler(resultA, …));
    };
}
function makeBhandler(resultA, …) {
    return function(resultB) {
        // more processing
        return // anything that uses the variables in scope
    };
}

Bạn cũng có thể sử dụng các hàm trợ giúp cho loại ứng dụng một phần này , như _.partialtừ Underscore / lodash hoặc phương thức gốc.bind() , để giảm thêm thụt lề:

function getExample() {
    // preprocessing
    return promiseA(…).then(handlerA);
}
function handlerA(resultA) {
    // some processing
    return promiseB(…).then(handlerB.bind(null, resultA));
}
function handlerB(resultA, resultB) {
    // more processing
    return // anything that uses resultA and resultB
}

5
Gợi ý tương tự này được đưa ra như là giải pháp cho 'Lỗi nâng cao # 4' trong bài viết của Nolan Lawson về những lời hứa túidb.com / 2015/05/18 / we-have-a-pro Hiệu-with-protises.html . Đó là một đọc tốt.
Robert

2
Đây chính xác là bindchức năng trong Monads. Haskell cung cấp đường cú pháp (ký hiệu) để làm cho nó trông giống như cú pháp async / await.
zeronone

50

Rõ ràng thông qua

Tương tự như lồng các hàm gọi lại, kỹ thuật này dựa vào các bao đóng. Tuy nhiên, chuỗi vẫn không thay đổi - thay vì chỉ chuyển kết quả mới nhất, một số đối tượng trạng thái được truyền cho mỗi bước. Các đối tượng trạng thái này tích lũy kết quả của các hành động trước đó, bàn giao tất cả các giá trị sẽ cần sau này cộng với kết quả của nhiệm vụ hiện tại.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(b => [resultA, b]); // function(b) { return [resultA, b] }
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Ở đây, mũi tên nhỏ đó b => [resultA, b]là hàm đóng lại resultAvà chuyển một mảng của cả hai kết quả sang bước tiếp theo. Mà sử dụng cú pháp hủy tham số để phá vỡ nó trong các biến đơn.

Trước khi phá hủy đã có sẵn với ES6, một phương thức trợ giúp tiện lợi được gọi là .spread()được cung cấp bởi nhiều thư viện hứa hẹn ( Q , Bluebird , khi , đấm ). Nó nhận một hàm có nhiều tham số - một tham số cho mỗi phần tử mảng - sẽ được sử dụng như .spread(function(resultA, resultB) { ….

Tất nhiên, việc đóng cửa cần thiết ở đây có thể được đơn giản hóa hơn nữa bởi một số chức năng của trình trợ giúp, ví dụ:

function addTo(x) {
    // imagine complex `arguments` fiddling or anything that helps usability
    // but you get the idea with this simple one:
    return res => [x, res];
}


return promiseB(…).then(addTo(resultA));

Ngoài ra, bạn có thể sử dụng Promise.allđể tạo lời hứa cho mảng:

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return Promise.all([resultA, promiseB(…)]); // resultA will implicitly be wrapped
                                                    // as if passed to Promise.resolve()
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Và bạn có thể không chỉ sử dụng các mảng, mà các đối tượng phức tạp tùy ý. Ví dụ: có _.extendhoặc Object.assigntrong một hàm trợ giúp khác:

function augment(obj, name) {
    return function (res) { var r = Object.assign({}, obj); r[name] = res; return r; };
}

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(augment({resultA}, "resultB"));
    }).then(function(obj) {
        // more processing
        return // something using both obj.resultA and obj.resultB
    });
}

Trong khi mô hình này đảm bảo một chuỗi phẳng và các đối tượng trạng thái rõ ràng có thể cải thiện sự rõ ràng, nó sẽ trở nên tẻ nhạt đối với một chuỗi dài. Đặc biệt khi bạn chỉ cần nhà nước lẻ tẻ, bạn vẫn phải vượt qua từng bước. Với giao diện cố định này, các cuộc gọi lại trong chuỗi được kết hợp khá chặt chẽ và không thể thay đổi. Nó làm cho việc thực hiện các bước đơn trở nên khó khăn hơn và các cuộc gọi lại không thể được cung cấp trực tiếp từ các mô-đun khác - chúng luôn cần được bọc trong mã soạn sẵn quan tâm đến trạng thái. Các chức năng trợ giúp trừu tượng như trên có thể làm giảm cơn đau một chút, nhưng nó sẽ luôn hiện diện.


Trước tiên, tôi không nghĩ rằng cú pháp bỏ qua Promise.allnên được khuyến khích (nó sẽ không làm việc trong ES6 khi destructuring sẽ thay thế nó và chuyển đổi một .spreadđến một thenmang đến cho người ta thường kết quả bất ngờ Tính đến tăng -. Tôi không chắc chắn lý do tại sao bạn cần sử dụng sự gia tăng - thêm mọi thứ vào nguyên mẫu lời hứa không phải là một cách có thể chấp nhận để mở rộng các lời hứa ES6 dù sao được cho là sẽ được gia hạn với phân lớp (hiện không được hỗ trợ).
Benjamin Gruenbaum

@BenjaminGruenbaum: Ý bạn là gì khi " bỏ qua cú phápPromise.all "? Không có phương pháp nào trong câu trả lời này sẽ phá vỡ với ES6. Chuyển đổi spreadsang phá hủy thencũng không có vấn đề. Re .prototype.auribution: Tôi biết ai đó sẽ chú ý đến nó, tôi chỉ thích khám phá các khả năng - sẽ chỉnh sửa nó.
Bergi

Theo cú pháp mảng, ý tôi là return [x,y]; }).spread(...thay vì return Promise.all([x, y]); }).spread(...nó sẽ không thay đổi khi hoán đổi lây lan cho đường phá hủy es6 và cũng không phải là trường hợp cạnh kỳ lạ khi lời hứa đối xử với mảng trả về khác với mọi thứ khác.
Benjamin Gruenbaum

3
Đây có lẽ là câu trả lời tốt nhất. Lời hứa là ánh sáng "Lập trình phản ứng chức năng" và đây thường là giải pháp được sử dụng. Ví dụ: BaconJs, có #combineTemplate cho phép bạn kết hợp các kết quả thành một đối tượng được truyền xuống chuỗi
U Avalos

1
@CapiEtheriel Câu trả lời được viết khi ES6 không được phổ biến rộng rãi như ngày nay. Vâng, có lẽ đã đến lúc trao đổi các ví dụ
Bergi

35

Trạng thái bối cảnh có thể thay đổi

Giải pháp tầm thường (nhưng không hay và khá lỗi) là chỉ sử dụng các biến phạm vi cao hơn (mà tất cả các cuộc gọi lại trong chuỗi có quyền truy cập) và viết giá trị kết quả cho chúng khi bạn nhận được chúng:

function getExample() {
    var resultA;
    return promiseA(…).then(function(_resultA) {
        resultA = _resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both resultA and resultB
    });
}

Thay vì nhiều biến, người ta cũng có thể sử dụng một đối tượng (ban đầu trống), trên đó các kết quả được lưu trữ dưới dạng các thuộc tính được tạo động.

Giải pháp này có một số nhược điểm:

  • Trạng thái đột biến là xấu , và các biến toàn cầu là xấu .
  • Mẫu này không hoạt động trên các ranh giới chức năng, việc mô đun hóa các chức năng khó hơn vì các khai báo của chúng không được rời khỏi phạm vi được chia sẻ
  • Phạm vi của các biến không ngăn chặn truy cập chúng trước khi chúng được khởi tạo. Điều này đặc biệt có khả năng đối với các công trình hứa hẹn phức tạp (vòng lặp, phân nhánh, trích dẫn) trong đó điều kiện cuộc đua có thể xảy ra. Vượt qua trạng thái rõ ràng, một thiết kế khai báo hứa hẹn khuyến khích, buộc một phong cách mã hóa sạch hơn có thể ngăn chặn điều này.
  • Người ta phải chọn phạm vi cho các biến được chia sẻ chính xác. Nó cần phải là cục bộ của hàm được thực thi để ngăn chặn các điều kiện chạy đua giữa nhiều lần gọi song song, như trường hợp, ví dụ, nếu trạng thái được lưu trữ trên một thể hiện.

Thư viện Bluebird khuyến khích việc sử dụng một đối tượng được truyền qua, sử dụng phương thức của chúngbind() để gán một đối tượng bối cảnh cho chuỗi hứa hẹn. Nó sẽ có thể truy cập được từ mỗi chức năng gọi lại thông qua thistừ khóa không sử dụng được . Mặc dù các thuộc tính đối tượng dễ bị lỗi chính tả hơn các biến, nhưng mẫu này khá thông minh:

function getExample() {
    return promiseA(…)
    .bind({}) // Bluebird only!
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }).bind(); // don't forget to unbind the object if you don't want the
               // caller to access it
}

Cách tiếp cận này có thể được mô phỏng dễ dàng trong các thư viện hứa không hỗ trợ .bind (mặc dù theo cách hơi dài dòng hơn và không thể được sử dụng trong biểu thức):

function getExample() {
    var ctx = {};
    return promiseA(…)
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }.bind(ctx)).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }.bind(ctx));
}

.bind()là không cần thiết để ngăn chặn rò rỉ bộ nhớ
Esailija

@Esailija: Nhưng không phải lời hứa trả lại có tham chiếu đến đối tượng bối cảnh không? OK, tất nhiên bộ sưu tập rác sẽ xử lý nó sau; đó không phải là "rò rỉ" trừ khi lời hứa không bao giờ được xử lý.
Bergi

Có nhưng lời hứa cũng giữ tham chiếu đến các giá trị thực hiện và lý do lỗi của họ ... nhưng không có gì liên quan đến lời hứa nên không thành vấn đề
Esailija

4
Hãy chia câu trả lời này thành hai vì tôi gần như đã bỏ phiếu trên phần mở đầu! Tôi nghĩ rằng "giải pháp tầm thường (nhưng không linh hoạt và khá sai sót)" là giải pháp đơn giản và đơn giản nhất, vì nó không dựa nhiều vào trạng thái đóng và trạng thái có thể thay đổi so với câu trả lời tự chấp nhận của bạn, nhưng đơn giản hơn. Đóng cửa không phải là toàn cầu cũng không phải là xấu xa. Các lập luận chống lại cách tiếp cận này không có ý nghĩa với tôi đưa ra tiền đề. Những vấn đề mô đun hóa nào có thể được đưa ra một "chuỗi hứa hẹn phẳng dài tuyệt vời"?
jib

2
Như tôi đã nói ở trên, Promise là "Lập trình phản ứng chức năng". Đây là một mô hình chống trong FRP
U Avalos

15

Một vòng quay ít khắc nghiệt hơn về "Trạng thái bối cảnh có thể thay đổi"

Sử dụng một đối tượng trong phạm vi cục bộ để thu thập các kết quả trung gian trong chuỗi hứa hẹn là một cách tiếp cận hợp lý cho câu hỏi bạn đặt ra. Hãy xem xét đoạn trích sau:

function getExample(){
    //locally scoped
    const results = {};
    return promiseA(paramsA).then(function(resultA){
        results.a = resultA;
        return promiseB(paramsB);
    }).then(function(resultB){
        results.b = resultB;
        return promiseC(paramsC);
    }).then(function(resultC){
        //Resolve with composite of all promises
        return Promise.resolve(results.a + results.b + resultC);
    }).catch(function(error){
        return Promise.reject(error);
    });
}
  • Biến toàn cục là xấu, vì vậy giải pháp này sử dụng biến có phạm vi cục bộ không gây hại. Nó chỉ có thể truy cập trong chức năng.
  • Trạng thái đột biến là xấu, nhưng điều này không biến đổi trạng thái một cách xấu xí. Theo truyền thống, trạng thái biến đổi xấu xí đề cập đến việc sửa đổi trạng thái của các đối số hàm hoặc biến toàn cục, nhưng cách tiếp cận này chỉ đơn giản là sửa đổi trạng thái của một biến phạm vi cục bộ tồn tại cho mục đích duy nhất là tổng hợp kết quả lời hứa ... một biến sẽ chết một cách đơn giản một khi lời hứa được giải quyết.
  • Các lời hứa trung gian không bị ngăn truy cập vào trạng thái của đối tượng kết quả, nhưng điều này không đưa ra một kịch bản đáng sợ nào khi một trong những lời hứa trong chuỗi sẽ lừa đảo và phá hoại kết quả của bạn. Trách nhiệm thiết lập các giá trị trong mỗi bước của lời hứa được giới hạn trong chức năng này và kết quả chung sẽ là chính xác hoặc không chính xác ... sẽ không có lỗi nào sẽ xuất hiện nhiều năm sau trong sản xuất (trừ khi bạn có ý định !)
  • Điều này không đưa ra một kịch bản điều kiện cuộc đua sẽ phát sinh từ việc gọi song song vì một trường hợp mới của biến kết quả được tạo cho mỗi lần gọi hàm getExample.

1
Ít nhất là tránh các Promiseantipotype xây dựng !
Bergi

Cảm ơn @Bergi, tôi thậm chí không nhận ra đó là một mô hình chống cho đến khi bạn đề cập đến nó!
Jay

đây là một cách giải quyết tốt để giảm thiểu lỗi liên quan đến lời hứa. Tôi đã sử dụng ES5 và không muốn thêm một thư viện khác để làm việc với lời hứa.
nilakantha singh deo

8

Node 7.4 hiện hỗ trợ async / chờ cuộc gọi với cờ hài hòa.

Thử cái này:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

và chạy tệp với:

node --harmony-async-await getExample.js

Đơn giản như có thể!


8

Những ngày này, tôi cũng gặp một số câu hỏi như bạn. Cuối cùng, tôi tìm thấy một giải pháp tốt với câu hỏi, nó đơn giản và tốt để đọc. Tôi hy vọng điều này có thể giúp bạn.

Theo lời hứa làm thế nào để chuỗi

ok, hãy nhìn vào mã:

const firstPromise = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('first promise is completed');
            resolve({data: '123'});
        }, 2000);
    });
};

const secondPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('second promise is completed');
            resolve({newData: `${someStuff.data} some more data`});
        }, 2000);
    });
};

const thirdPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('third promise is completed');
            resolve({result: someStuff});
        }, 2000);
    });
};

firstPromise()
    .then(secondPromise)
    .then(thirdPromise)
    .then(data => {
        console.log(data);
    });

4
Điều này không thực sự trả lời câu hỏi về cách truy cập các kết quả trước đó trong chuỗi.
Bergi

2
Mỗi lời hứa có thể nhận được giá trị trước đó, ý nghĩa của bạn là gì?
yzfdjzwl

1
Hãy xem mã trong câu hỏi. Mục đích không phải là để có được kết quả của lời hứa .thenđược gọi, mà là kết quả từ trước đó. Ví dụ: thirdPromisetruy cập kết quả của firstPromise.
Bergi

6

Một câu trả lời khác, sử dụng babel-nodephiên bản <6

Sử dụng async - await

npm install -g babel@5.6.14

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Sau đó, chạy babel-node example.jsvà thì đấy!


1
Có tôi đã làm, ngay sau khi tôi đăng bài của tôi. Tuy nhiên, tôi sẽ rời bỏ nó bởi vì nó giải thích cách thực sự đứng dậy và chạy với việc sử dụng ES7 thay vì chỉ nói rằng một ngày nào đó ES7 sẽ có sẵn.
Anthony

1
Ồ đúng rồi, tôi nên cập nhật câu trả lời của mình để nói rằng các plugin "thử nghiệm" cho những plugin này đã có ở đây.
Bergi

2

Tôi sẽ không sử dụng mẫu này trong mã của riêng mình vì tôi không phải là một fan hâm mộ lớn của việc sử dụng các biến toàn cục. Tuy nhiên, trong một nhúm nó sẽ hoạt động.

Người dùng là một mô hình Mongoose được hứa hẹn.

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});

2
Lưu ý rằng mẫu này đã được chi tiết trong câu trả lời trạng thái ngữ cảnh Mutable (và cả lý do tại sao nó xấu - tôi cũng không phải là một fan hâm mộ lớn)
Bergi

Trong trường hợp của bạn, mô hình dường như là vô dụng mặc dù. Bạn không cần một globalVarchút, chỉ cần làm gì User.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });?
Bergi

1
Tôi không cần mã cá nhân trong mã riêng của mình, nhưng người dùng có thể cần chạy nhiều async hơn trong chức năng thứ hai và sau đó tương tác với cuộc gọi Promise ban đầu. Nhưng như đã đề cập, tôi sẽ sử dụng máy phát điện trong trường hợp này. :)
Anthony

2

Một câu trả lời khác, sử dụng trình thực thi tuần tự nsynjs :

function getExample(){

  var response1 = returnPromise1().data;

  // promise1 is resolved at this point, '.data' has the result from resolve(result)

  var response2 = returnPromise2().data;

  // promise2 is resolved at this point, '.data' has the result from resolve(result)

  console.log(response, response2);

}

nynjs.run(getExample,{},function(){
    console.log('all done');
})

Cập nhật: thêm ví dụ làm việc

function synchronousCode() {
     var urls=[
         "https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"
     ];
     for(var i=0; i<urls.length; i++) {
         var len=window.fetch(urls[i]).data.text().data.length;
         //             ^                   ^
         //             |                   +- 2-nd promise result
         //             |                      assigned to 'data'
         //             |
         //             +-- 1-st promise result assigned to 'data'
         //
         console.log('URL #'+i+' : '+urls[i]+", length: "+len);
     }
}

nsynjs.run(synchronousCode,{},function(){
    console.log('all done');
})
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>


1

Khi sử dụng bluebird, bạn có thể sử dụng .bindphương thức để chia sẻ các biến trong chuỗi hứa hẹn:

somethingAsync().bind({})
.spread(function (aValue, bValue) {
    this.aValue = aValue;
    this.bValue = bValue;
    return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
    return this.aValue + this.bValue + cValue;
});

vui lòng kiểm tra liên kết này để biết thêm thông tin:

http://bluebirdjs.com/docs/api/promise.bind.html


Lưu ý rằng mẫu này đã được chi tiết trong câu trả lời trạng thái ngữ cảnh Mutable
Bergi

1
function getExample() {
    var retA, retB;
    return promiseA(…).then(function(resultA) {
        retA = resultA;
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        //retA is value of promiseA
        return // How do I gain access to resultA here?
    });
}

cách dễ dàng: D


Bạn đã nhận thấy câu trả lời này ?
Bergi

1

Tôi nghĩ bạn có thể sử dụng hàm băm của RSVP.

Một cái gì đó như dưới đây:

    const mainPromise = () => {
        const promise1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('first promise is completed');
                resolve({data: '123'});
            }, 2000);
        });

        const promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('second promise is completed');
                resolve({data: '456'});
            }, 2000);
        });

        return new RSVP.hash({
              prom1: promise1,
              prom2: promise2
          });

    };


   mainPromise()
    .then(data => {
        console.log(data.prom1);
        console.log(data.prom2);
    });

Vâng, đó là giống như các Promise.allgiải pháp , chỉ với một đối tượng thay vì một mảng.
Bergi

0

Giải pháp:

Bạn có thể đặt các giá trị trung gian trong phạm vi trong bất kỳ chức năng 'sau đó' nào sau đó, bằng cách sử dụng 'liên kết'. Đó là một giải pháp tốt đẹp không yêu cầu thay đổi cách Promise hoạt động và chỉ yêu cầu một hoặc hai dòng mã để truyền bá các giá trị giống như các lỗi đã được lan truyền.

Dưới đây là một ví dụ hoàn chỉnh:

// Get info asynchronously from a server
function pGetServerInfo()
    {
    // then value: "server info"
    } // pGetServerInfo

// Write into a file asynchronously
function pWriteFile(path,string)
    {
    // no then value
    } // pWriteFile

// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
    {
    var scope={localInfo:localInfo}; // Create an explicit scope object
    var thenFunc=p2.bind(scope); // Create a temporary function with this scope
    return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
    } // pLogInfo

// Scope of this 'then' function is {localInfo:localInfo}
function p2(serverInfo)
    {
    // Do the final 'then' in the chain: Writes "local info, server info"
    return pWriteFile('log',this.localInfo+','+serverInfo);
    } // p2

Giải pháp này có thể được gọi như sau:

pLogInfo("local info").then().catch(err);

(Lưu ý: phiên bản phức tạp và đầy đủ hơn của giải pháp này đã được thử nghiệm, nhưng không phải phiên bản ví dụ này, vì vậy nó có thể có lỗi.)


Đây dường như là mô hình giống như trong câu trả lời đóng (và) lồng nhau
Bergi

Nó trông giống nhau. Từ đó tôi đã biết rằng cú pháp Async / Await mới bao gồm tự động ràng buộc các đối số, vì vậy tất cả các đối số đều có sẵn cho tất cả các hàm không đồng bộ. Tôi đang từ bỏ Lời hứa.
David Spector

async/ awaitvẫn có nghĩa là sử dụng lời hứa. Những gì bạn có thể từ bỏ là thencác cuộc gọi với cuộc gọi lại.
Bergi

-1

Những gì tôi học được về các lời hứa là chỉ sử dụng nó làm giá trị trả về tránh tham chiếu chúng nếu có thể. Cú pháp async / await đặc biệt thiết thực cho điều đó. Ngày nay, tất cả các trình duyệt và nút mới nhất đều hỗ trợ nó: https://caniuse.com/#feat=async-fifts , là một hành vi đơn giản và mã giống như đọc mã đồng bộ, quên đi các cuộc gọi lại ...

Trong trường hợp tôi cần tham khảo một lời hứa là khi việc tạo và giải quyết xảy ra ở những nơi độc lập / không liên quan. Vì vậy, thay vì một hiệp hội nhân tạo và có lẽ là một người nghe sự kiện chỉ để giải quyết lời hứa "xa vời", tôi thích thể hiện lời hứa như một Trì hoãn, mà đoạn mã sau thực hiện nó trong es5 hợp lệ

/**
 * Promise like object that allows to resolve it promise from outside code. Example:
 *
```
class Api {
  fooReady = new Deferred<Data>()
  private knower() {
    inOtherMoment(data=>{
      this.fooReady.resolve(data)
    })
  }
}
```
 */
var Deferred = /** @class */ (function () {
  function Deferred(callback) {
    var instance = this;
    this.resolve = null;
    this.reject = null;
    this.status = 'pending';
    this.promise = new Promise(function (resolve, reject) {
      instance.resolve = function () { this.status = 'resolved'; resolve.apply(this, arguments); };
      instance.reject = function () { this.status = 'rejected'; reject.apply(this, arguments); };
    });
    if (typeof callback === 'function') {
      callback.call(this, this.resolve, this.reject);
    }
  }
  Deferred.prototype.then = function (resolve) {
    return this.promise.then(resolve);
  };
  Deferred.prototype.catch = function (r) {
    return this.promise.catch(r);
  };
  return Deferred;
}());

transpiled tạo thành một dự án bản thảo của tôi:

https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41353b/misc-utils-of-mine-generic/src/promise.ts

Đối với các trường hợp phức tạp hơn, tôi thường sử dụng các tiện ích hứa hẹn nhỏ này mà không phụ thuộc vào kiểm tra và đánh máy. p-map đã được sử dụng nhiều lần. Tôi nghĩ rằng anh ấy bao gồm hầu hết các trường hợp sử dụng:

https://github.com/sindresorhus?utf8=%E2%9C%93&tab=reposearies&q=promise&type=source&lingu=


Âm thanh như bạn đang đề nghị hoặc là trạng thái bối cảnh có thể thay đổi hoặc kiểm tra đồng bộ ?
Bergi

@bergi Lần đầu tiên tôi đứng đầu những cái tên đó Tôi thường cần mô hình này trong những trường hợp mà trách nhiệm tạo và giải quyết lời hứa là độc lập nên không cần liên hệ với họ chỉ để giải quyết lời hứa. Tôi thích nghi nhưng không phải ví dụ của bạn, và sử dụng một lớp, nhưng có thể tương đương.
Cancerbero
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.