Làm thế nào để bạn trả về đúng nhiều giá trị từ một lời hứa?


86

Gần đây tôi đã gặp một vài tình huống nhất định mà tôi không biết phải giải quyết như thế nào cho hợp lý. Giả sử đoạn mã sau:

somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( amazingData ) {
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
}

Bây giờ một tình huống có thể phát sinh mà tôi muốn có quyền truy cập amazingDatavào afterSomethingElse.

Một giải pháp rõ ràng là trả về một mảng hoặc một hàm băm afterSomething, bởi vì, bạn chỉ có thể trả về một giá trị từ một hàm. Nhưng tôi tự hỏi liệu có cách nào để afterSomethingElsechấp nhận 2 tham số và gọi nó tương tự không, vì điều đó có vẻ dễ dàng hơn rất nhiều để tài liệu và hiểu.

Tôi chỉ tự hỏi về khả năng này vì có Q.spread, nó thực hiện điều gì đó tương tự như những gì tôi muốn.




Bỏ cấu trúc Bài tập trong ES6 sẽ hữu ích. kiểm tra ở đây
Ravi Teja

Câu trả lời:


88

Bạn không thể giải quyết một lời hứa với nhiều thuộc tính giống như bạn không thể trả về nhiều giá trị từ một hàm . Một lời hứa đại diện cho một giá trị theo thời gian về mặt khái niệm, vì vậy trong khi bạn có thể biểu diễn các giá trị tổng hợp, bạn không thể đặt nhiều giá trị vào một lời hứa.

Một lời hứa vốn dĩ phân giải với một giá trị duy nhất - đây là một phần của cách Q hoạt động, cách hoạt động của thông số Promises / A + và cách trừu tượng hóa hoạt động.

Cách gần nhất bạn có thể nhận được là sử dụng Q.spreadvà trả về mảng hoặc sử dụng cấu trúc ES6 nếu nó được hỗ trợ hoặc bạn sẵn sàng sử dụng một công cụ chuyển đổi như BabelJS.

Để chuyển ngữ cảnh xuống một chuỗi hứa hẹn, vui lòng tham khảo tài liệu kinh điển tuyệt vời của Bergi về điều đó .


16
Có gì sai khi giải quyết với một đối tượng có nhiều thuộc tính? Có vẻ như một cách đơn giản để lấy nhiều giá trị ra khỏi một giải pháp.
jfriend00

6
Đó là hoàn toàn tốt đẹp để làm điều đó
Benjamin Gruenbaum

Bạn cũng có thể mở rộng Promise có .spread()như Bluebird thể hiện trong câu trả lời liên quan này: stackoverflow.com/a/22776850/1624862
Kevin Ghadyani

Hành vi của Promise.all () dường như mâu thuẫn với điều này. Promise.all([a, b, c]).then(function(x, y, z) {...})hoạt động chính xác trong tất cả các công cụ Javascript hiện đại với x, y và z đánh giá các giá trị đã giải quyết của a, b và c. Vì vậy, chính xác hơn khi nói rằng ngôn ngữ không cho phép bạn làm điều đó dễ dàng (hoặc lành mạnh) từ mã người dùng (bởi vì bạn có thể trả về một Lời hứa trực tiếp từ mệnh đề then, bạn có thể bọc các giá trị của mình trong các lời hứa và sau đó bọc chúng bằng Lời hứa .all () để có được hành vi mong muốn, mặc dù theo cách phức tạp).
Austin Hemmelgarn

3
@AustinHemmelgarn Điều đó chỉ là sai, Promise.allđáp ứng với một mảng . Trong Promise.all([a,b]).then((a, b) => bundefined. Đó là lý do tại sao bạn cần làm .then(([a, b]) =>đó là một nhiệm vụ destructuring
Benjamin Gruenbaum

38

bạn chỉ có thể chuyển một giá trị, nhưng nó có thể là một mảng có nhiều giá trị bên trong, chẳng hạn như:

function step1(){
  let server = "myserver.com";
  let data = "so much data, very impresive";
  return Promise.resolve([server, data]);
}

mặt khác, bạn có thể sử dụng biểu thức hủy cấu trúc cho ES2015 để nhận các giá trị riêng lẻ.

function step2([server, data]){
  console.log(server); // print "myserver.com"
  console.log(data);   // print "so much data, very impresive"
  return Promise.resolve("done");
}

để gọi cả hai lời hứa, xâu chuỗi chúng:

step1()
.then(step2)
.then((msg)=>{
  console.log(msg); // print "done"
})

5
Nó được gọi là hủy cấu trúc, không phải "giải mã" và nó không phải là toán tử: - /
Bergi

3
Btw, bạn có thể sử dụng hàm hủy ngay trong các tham số: function step2([server, data]) { …- theo cách đó bạn cũng tránh gán cho các hình cầu ngầm. Và bạn thực sự nên sử dụng returnhoặc Promise.resolve, không phải hàm new Promisetạo trong các ví dụ của bạn.
Bergi

thans @Bergi cho các gợi ý!
Alejandro Silva

19

Bạn có thể trả về một đối tượng chứa cả hai giá trị - không có gì sai với điều đó.

Một chiến lược khác là giữ giá trị, thông qua các lần đóng, thay vì chuyển nó qua:

somethingAsync().then(afterSomething);

function afterSomething(amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

Dạng hoàn toàn thay vì nội dòng một phần (tương đương, được cho là nhất quán hơn):

somethingAsync().then(function (amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

3
Nó là ok để trả lại một thenbên trong khác then? Nó không phải là một mô hình chống ?
robe007

như @ robe007 đã nói, điều này có tương tự như 'địa ngục gọi lại' không? ở đây việc làm tổ của bạn sau đó chặn thay vì các hàm gọi lại, điều này sẽ đánh bại mục đích của việc có những lời hứa
Dheeraj

5

Hai điều bạn có thể làm, trả lại một đối tượng

somethingAsync()
    .then( afterSomething )
    .then( afterSomethingElse );

function processAsync (amazingData) {
     //processSomething
     return {
         amazingData: amazingData, 
         processedData: processedData
     };
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}

function afterSomethingElse( dataObj ) {
    let amazingData = dataObj.amazingData,
        processedData = dataObj.proccessedData;
}

Sử dụng phạm vi!

var amazingData;
somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( returnedAmazingData ) {
  amazingData = returnedAmazingData;
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
  //use amazingData here
}

3

Đây là cách tôi nghĩ bạn nên làm.

tách chuỗi

Bởi vì cả hai chức năng sẽ được sử dụng AmazingData , điều hợp lý là có chúng trong một chức năng chuyên dụng. Tôi thường làm điều đó mỗi khi tôi muốn sử dụng lại một số dữ liệu, vì vậy nó luôn hiện diện dưới dạng một hàm đối số.

Vì ví dụ của bạn đang chạy một số mã, tôi sẽ cho rằng nó được khai báo tất cả bên trong một hàm. Tôi sẽ gọi nó là toto () . Sau đó, chúng ta sẽ có một hàm khác sẽ chạy cả afterSomething ()afterSomethingElse () .

function toto() {
    return somethingAsync()
        .then( tata );
}

Bạn cũng sẽ nhận thấy rằng tôi đã thêm một câu lệnh trả lại vì nó thường là cách để đi với Promises - bạn luôn trả lại một lời hứa để chúng tôi có thể tiếp tục xâu chuỗi nếu cần. Ở đây, somethingAsync () sẽ tạo ra AmazingData và nó sẽ có sẵn ở mọi nơi bên trong hàm mới.

Bây giờ, hàm mới này sẽ trông như thế nào thường phụ thuộc vào processAsync () cũng không đồng bộ ?

processAsync không bất đồng bộ

Không có lý do gì để phức tạp hóa mọi thứ nếu processAsync () không phải là không đồng bộ. Một số mã tuần tự tốt cũ sẽ tạo ra nó.

function tata( amazingData ) {
    var processed = afterSomething( amazingData );
    return afterSomethingElse( amazingData, processed );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

Lưu ý rằng không quan trọng nếu afterSomethingElse () đang làm điều gì đó không đồng bộ hay không. Nếu đúng như vậy, một lời hứa sẽ được trả lại và chuỗi có thể tiếp tục. Nếu không, thì giá trị kết quả sẽ được trả về. Nhưng vì hàm được gọi từ then () , giá trị sẽ được bao bọc thành một lời hứa (ít nhất là trong Javascript thô).

processAsync không đồng bộ

Nếu processAsync () không đồng bộ, mã sẽ trông hơi khác một chút. Ở đây chúng ta coi afterSomething ()afterSomethingElse () sẽ không được sử dụng lại ở bất kỳ nơi nào khác.

function tata( amazingData ) {
    return afterSomething()
        .then( afterSomethingElse );

    function afterSomething( /* no args */ ) {
        return processAsync( amazingData );
    }
    function afterSomethingElse( processedData ) {
        /* amazingData can be accessed here */
    }
}

Tương tự như trước cho afterSomethingElse () . Nó có thể không đồng bộ hoặc không. Một lời hứa sẽ được trả lại, hoặc một giá trị được bao bọc thành một lời hứa đã được giải quyết.


Phong cách viết mã của bạn khá gần với những gì tôi thường làm, đó là lý do tại sao tôi đã trả lời ngay cả sau 2 năm. Tôi không phải là một fan hâm mộ lớn của việc có các chức năng ẩn danh ở khắp mọi nơi. Tôi thấy nó khó đọc. Ngay cả khi nó khá phổ biến trong cộng đồng. Nó giống như chúng ta đã thay thế địa ngục gọi lại bằng một luyện ngục hứa hẹn .

Tôi cũng muốn giữ tên của các chức năng trong đó ngắn. Chúng sẽ chỉ được định nghĩa cục bộ. Và hầu hết thời gian họ sẽ gọi một hàm khác được định nghĩa ở nơi khác - để có thể tái sử dụng - để thực hiện công việc. Tôi thậm chí còn làm điều đó đối với các hàm chỉ có 1 tham số, vì vậy tôi không cần phải nhập và xuất hàm khi thêm / bớt một tham số vào chữ ký hàm.

Ví dụ ăn uống

Đây là một ví dụ:

function goingThroughTheEatingProcess(plenty, of, args, to, match, real, life) {
    return iAmAsync()
        .then(chew)
        .then(swallow);

        function chew(result) {
            return carefullyChewThis(plenty, of, args, "water", "piece of tooth", result);
        }

        function swallow(wine) {
            return nowIsTimeToSwallow(match, real, life, wine);
        }
}

function iAmAsync() {
    return Promise.resolve("mooooore");
}

function carefullyChewThis(plenty, of, args, and, some, more) {
    return true;
}

function nowIsTimeToSwallow(match, real, life, bobool) {
}

Đừng tập trung quá nhiều vào Promise.resolve () . Nó chỉ là một cách nhanh chóng để tạo ra một lời hứa đã được giải quyết. Những gì tôi cố gắng đạt được bằng cách này là có tất cả mã tôi đang chạy ở một vị trí duy nhất - ngay bên dưới chúng . Tất cả các chức năng khác có tên mô tả hơn đều có thể sử dụng lại được.

Hạn chế của kỹ thuật này là nó đang xác định rất nhiều chức năng. Nhưng đó là một nỗi đau cần thiết mà tôi sợ để tránh có những chức năng ẩn danh khắp nơi. Và rủi ro là gì: tràn ngăn xếp? (đùa!)


Sử dụng mảng hoặc đối tượng như được định nghĩa trong các câu trả lời khác cũng sẽ hoạt động. Theo một cách nào đó, đây là câu trả lời do Kevin Reid đề xuất .

Bạn cũng có thể sử dụng bind () hoặc Promise.all () . Lưu ý rằng họ vẫn sẽ yêu cầu bạn tách mã của mình.

sử dụng ràng buộc

Nếu bạn muốn giữ các hàm của mình có thể sử dụng lại được nhưng không thực sự cần giữ lại những gì bên trong thì bạn có thể sử dụng bind () .

function tata( amazingData ) {
    return afterSomething( amazingData )
        .then( afterSomethingElse.bind(null, amazingData) );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

Để đơn giản, bind () sẽ thêm trước danh sách các args (ngoại trừ args đầu tiên) vào hàm khi nó được gọi.

sử dụng Promise.all

Trong bài đăng của bạn, bạn đã đề cập đến việc sử dụng spread () . Tôi chưa bao giờ sử dụng framework mà bạn đang sử dụng, nhưng đây là cách bạn có thể sử dụng nó.

Một số người tin rằng Promise.all () là giải pháp cho mọi vấn đề, vì vậy tôi đoán nó xứng đáng được đề cập.

function tata( amazingData ) {
    return Promise.all( [ amazingData, afterSomething( amazingData ) ] )
        .then( afterSomethingElse );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( args ) {
    var amazingData = args[0];
    var processedData = args[1];
}

Bạn có thể chuyển dữ liệu đến Promise.all () - lưu ý sự hiện diện của mảng - miễn là các hứa, nhưng đảm bảo không có hứa nào bị lỗi nếu không nó sẽ ngừng xử lý.

Và thay vì xác định các biến mới từ đối số args , bạn có thể sử dụng spread () thay vì then () cho tất cả các loại công việc tuyệt vời.


3

Đơn giản chỉ cần tạo một đối tượng và trích xuất các đối số từ đối tượng đó.

let checkIfNumbersAddToTen = function (a, b) {
return new Promise(function (resolve, reject) {
 let c = parseInt(a)+parseInt(b);
 let promiseResolution = {
     c:c,
     d : c+c,
     x : 'RandomString'
 };
 if(c===10){
     resolve(promiseResolution);
 }else {
     reject('Not 10');
 }
});
};

Kéo các đối số từ promiseResolution.

checkIfNumbersAddToTen(5,5).then(function (arguments) {
console.log('c:'+arguments.c);
console.log('d:'+arguments.d);
console.log('x:'+arguments.x);
},function (failure) {
console.log(failure);
});

2

Bất cứ điều gì bạn trở lại từ một lời hứa sẽ được gói gọn thành một lời hứa không được bao bọc ở .then()giai đoạn tiếp theo .

Nó trở nên thú vị khi bạn cần trả về một hoặc nhiều (các) lời hứa cùng với một hoặc nhiều (các) giá trị đồng bộ như;

Promise.resolve([Promise.resolve(1), Promise.resolve(2), 3, 4])
       .then(([p1,p2,n1,n2]) => /* p1 and p2 are still promises */);

Trong những trường hợp này, điều cần thiết là sử dụng Promise.all()để nhận p1p2những lời hứa chưa được đóng gói ở .then()giai đoạn tiếp theo, chẳng hạn như

Promise.resolve(Promise.all([Promise.resolve(1), Promise.resolve(2), 3, 4]))
       .then(([p1,p2,n1,n2]) => /* p1 is 1, p2 is 2, n1 is 3 and n2 is 4 */);

1

Bạn có thể kiểm tra Observable được đại diện bởi Rxjs , cho phép bạn trả về nhiều hơn một giá trị.


0

Đơn giản chỉ cần trả lại một tuple:

async add(dto: TDto): Promise<TDto> {
console.log(`${this.storeName}.add(${dto})`);
return firebase.firestore().collection(this.dtoName)
  .withConverter<TDto>(this.converter)
  .add(dto)
  .then(d => [d.update(this.id, d.id), d.id] as [any, string])
  .then(x => this.get(x[1]));

}

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.