Tại sao hàm không đồng bộ của tôi trả về Promise {<pend>} thay vì một giá trị?


128

Mã của tôi:

let AuthUser = data => {
  return google.login(data.username, data.password).then(token => { return token } )
}

Và khi tôi cố gắng chạy một cái gì đó như thế này:

let userToken = AuthUser(data)
console.log(userToken)

Tôi nhận được:

Promise { <pending> }

Nhưng tại sao?

Mục tiêu chính của tôi là lấy mã thông báo google.login(data.username, data.password)mà từ đó trả về một lời hứa, thành một biến. Và chỉ sau đó thực hiện một số hành động.


1
@ LoïcFaure-Lacroix, xem bài viết này: medium.com/@bluepnume/…
Src

@ LoïcFaure-Lacroix nhìn vào getFirstUserchức năng
Src

Vậy còn nó thì sao? Đó là một hàm trả về một lời hứa.
Loïc Faure-Lacroix

1
@ LoïcFaure-Lacroix, vậy ý ​​bạn là ngay cả trong ví dụ đó chúng ta cũng cần sử dụng thì để truy cập lời hứa dữ liệu trả về trong hàm getFirstUser?
Src

Trong ví dụ đó có, cách duy nhất khác là sử dụng cú pháp ES7 "await" có vẻ như để giải quyết việc dừng thực thi ngữ cảnh hiện tại để chờ kết quả của lời hứa. Nếu bạn đọc bài viết bạn sẽ thấy nó. Nhưng vì ES7 có lẽ hầu như không được hỗ trợ, vâng. "Sau đó" là khá nhiều nó.
Loïc Faure-Lacroix

Câu trả lời:


175

Lời hứa sẽ luôn ghi nhật ký chờ xử lý miễn là kết quả của nó vẫn chưa được giải quyết. Bạn phải gọi .thenlời hứa để nắm bắt kết quả bất kể trạng thái lời hứa (đã giải quyết hay vẫn đang chờ xử lý):

let AuthUser = function(data) {
  return google.login(data.username, data.password).then(token => { return token } )
}

let userToken = AuthUser(data)
console.log(userToken) // Promise { <pending> }

userToken.then(function(result) {
   console.log(result) // "Some User token"
})

Tại sao vậy?

Lời hứa chỉ là hướng về phía trước; Bạn chỉ có thể giải quyết chúng một lần. Giá trị đã phân giải của mộtPromise được chuyển cho .thenhoặc.catch các phương thức .

Chi tiết

Theo Promises / A + spec:

Thủ tục giải quyết lời hứa là một hoạt động trừu tượng lấy đầu vào là một lời hứa và một giá trị, chúng ta ký hiệu là [[Giải quyết]] (hứa hẹn, x). Nếu x là anable, nó cố gắng thực hiện lời hứa thông qua trạng thái của x, với giả định rằng x hoạt động ít nhất giống như một lời hứa. Nếu không, nó thực hiện lời hứa với giá trị x.

Việc xử lý các khả năng này cho phép các triển khai hứa hẹn tương tác với nhau, miễn là chúng hiển thị phương thức sau đó tuân thủ Promises / A +. Nó cũng cho phép triển khai Promises / A + “đồng hóa” các triển khai không phù hợp với các phương pháp then hợp lý.

Thông số này hơi khó để phân tích cú pháp, vì vậy hãy chia nhỏ nó. Quy tắc là:

Nếu hàm trong .thentrình xử lý trả về một giá trị, thì hàm Promisesẽ giải quyết với giá trị đó. Nếu trình xử lý trả về một giá trị khác Promise, thì bản gốc Promisesẽ phân giải với giá trị đã phân giải của chuỗi Promise. Trình .thenxử lý tiếp theo sẽ luôn chứa giá trị đã phân giải của lời hứa chuỗi được trả về ở phần trước .then.

Cách nó thực sự hoạt động được mô tả chi tiết hơn bên dưới:

1. Kết quả trả về của .thenhàm sẽ là giá trị được giải quyết của lời hứa.

function initPromise() {
  return new Promise(function(res, rej) {
    res("initResolve");
  })
}

initPromise()
  .then(function(result) {
    console.log(result); // "initResolve"
    return "normalReturn";
  })
  .then(function(result) {
    console.log(result); // "normalReturn"
  });

2. Nếu .thenhàm trả về a Promise, thì giá trị đã phân giải của lời hứa chuỗi đó được chuyển đến giá trị sau .then.

function initPromise() {
  return new Promise(function(res, rej) {
    res("initResolve");
  })
}

initPromise()
  .then(function(result) {
    console.log(result); // "initResolve"
    return new Promise(function(resolve, reject) {
       setTimeout(function() {
          resolve("secondPromise");
       }, 1000)
    })
  })
  .then(function(result) {
    console.log(result); // "secondPromise"
  });

Cái đầu tiên của bạn không hoạt động. Uncaught SyntaxError: Unexpected token .. Chuyến thứ hai cần trả lạiPromise
zamil

@zamil bạn phải gọi hàm, như trong ví dụ thứ hai. bạn không thể .thenvào một chức năng không được mời. đã cập nhật câu trả lời
Bamieh

1
Tôi đang đánh dấu trang này để có thể giữ nó mãi mãi. Tôi đã làm việc RẤT rất lâu để tìm ra các quy tắc thực sự rõ ràng và dễ đọc về cách thực sự xây dựng lời hứa. Blockquote về Promises / A + spec của bạn là một ví dụ hoàn hảo về lý do tại sao nó là PITA để tự dạy lời hứa. Đây cũng là lần DUY NHẤT tôi thấy setTimeout được sử dụng mà nó không gây nhầm lẫn cho chính bài học. Và tài liệu tham khảo tuyệt vời, cảm ơn bạn.
Monsto

21

Tôi biết câu hỏi này đã được hỏi cách đây 2 năm, nhưng tôi gặp phải vấn đề tương tự và câu trả lời cho vấn đề là kể từ ES6, bạn có thể chỉ cần awaitcác hàm trả về giá trị, như:

let AuthUser = function(data) {
  return google.login(data.username, data.password).then(token => { return token } )
}

let userToken = await AuthUser(data)
console.log(userToken) // your data

3
Bạn không cần .then(token => return token), đó chỉ là một thông báo không cần thiết. Đơn giản chỉ cần trả lại cuộc gọi đăng nhập google.
Soviut

Câu trả lời này không liên quan đến câu hỏi. Vấn đề của áp phích gốc không liên quan gì đến việc không đồng bộ / chờ đợi của ES6. Promises đã tồn tại trước khi đường cú pháp mới này được giới thiệu trong ECMAScript 2017 và họ đã sử dụng Promises "dưới mui xe". Xem MDN trên async / await .
try-catch-

Đối với ES8 / Nodejs, lỗi sẽ xảy ra nếu bạn sử dụng awaitbên ngoài một hàm không đồng bộ. Có lẽ ví dụ tốt hơn đây sẽ là để làm cho AuthUserchức năng async, mà mục đích sau đó vớireturn await google.login(...);
Jon L.

4

Các thentrở về phương pháp cấp phát một lời hứa mà có thể được giải quyết đồng bộ bởi các giá trị trả về của một handler kết quả đăng ký trong các cuộc gọi đếnthen , hoặc từ chối bằng cách ném một lỗi bên trong xử lý gọi.

Vì vậy, việc gọi AuthUsersẽ không đột nhiên đăng nhập đồng bộ người dùng, nhưng trả về một lời hứa mà các trình xử lý đã đăng ký sau đó sẽ được gọi sau khi đăng nhập thành công (hoặc không thành công). Tôi sẽ đề nghị kích hoạt tất cả quá trình đăng nhập bằng một thenđiều khoản của lời hứa đăng nhập. EG sử dụng các hàm được đặt tên để làm nổi bật trình tự của luồng:

let AuthUser = data => {   // just the login promise
  return google.login(data.username, data.password);
};

AuthUser(data).then( processLogin).catch(loginFail);

function processLogin( token) {
      // do logged in stuff:
      // enable, initiate, or do things after login
}
function loginFail( err) {
      console.log("login failed: " + err);
}

1

Xem phần MDN về Lời hứa. Đặc biệt, hãy nhìn vào kiểu trả về của then ().

Để đăng nhập, tác nhân người dùng phải gửi một yêu cầu đến máy chủ và đợi để nhận được phản hồi. Vì việc làm cho ứng dụng của bạn hoàn toàn ngừng thực thi trong một lượt yêu cầu thường gây ra trải nghiệm người dùng không tốt, thực tế mọi hàm JS mà bạn đăng nhập (hoặc thực hiện bất kỳ hình thức tương tác máy chủ nào khác) sẽ sử dụng Promise hoặc một cái gì đó rất giống nó , để cung cấp kết quả không đồng bộ.

Bây giờ, cũng lưu ý rằng các returncâu lệnh luôn được đánh giá trong ngữ cảnh của hàm mà chúng xuất hiện. Vì vậy, khi bạn viết:

let AuthUser = data => {
  return google
    .login(data.username, data.password)
    .then( token => {
      return token;
    });
};

câu lệnh return token;có nghĩa là hàm ẩn danh được chuyển vào then()sẽ trả về mã thông báo, chứ không phải AuthUserhàm nên trả về. Những gì AuthUsertrả về là kết quả của việc gọi google.login(username, password).then(callback);, tình cờ là một Lời hứa.

Cuối cùng thì cuộc gọi lại của bạn token => { return token; }không làm gì cả; thay vào đó, đầu vào của bạn then()cần phải là một hàm thực sự xử lý mã thông báo theo một cách nào đó.


@Src Tôi đã viết câu trả lời của mình trước khi người hỏi làm rõ rằng họ đang tìm cách trả về đồng bộ một giá trị và không đưa ra giả định về môi trường phát triển hoặc phiên bản ngôn ngữ của họ ngoài những gì có thể suy ra bởi đoạn mã - nghĩa là, nó an toàn giả sử ES6, nhưng không nhất thiết phải là ES7.
Jesse Amano

@AhmadBamieh Được rồi, sẽ làm được. Tôi giả định rằng vấn đề là tôi đã hiểu sai cách returnđược xử lý với cú pháp đóng mới (ish), trong trường hợp đó - tôi thực sự phản đối điều đó, nhưng lỗi vẫn là của tôi và tôi xin lỗi vì điều đó.
Jesse Amano

2
@AhmadBamieh Er, tôi thực sự đã biết phần đó, đó là lý do tại sao tôi khẳng định rằng điều đó token => { return token; } không có gì trái ngược với tuyên bố rằng nó phản tác dụng. Bạn có thể nói google.login(username, password).then(token=>{return token;}).then(token=>{return token;})và v.v. mãi mãi, nhưng bạn sẽ chỉ đạt được trả về một Promisegiải quyết bằng mã thông báo — giống như khi bạn chỉ để nó như vậy google.login(username, password);. Tôi không chắc tại sao bạn cảm thấy rằng điều này là "rất sai".
Jesse Amano

1
@AhmadBamieh: bạn có thể nói cụ thể hơn về điều gì sai trong đoạn văn bản này không? Tôi không thấy gì cả, anh ấy chỉ giải thích tại sao return tokenkhông hoạt động như mong đợi của OP.
Bergi

3
@AhmadBamieh: thực sự là có sự hiểu lầm. Cả ba chúng ta đều biết rõ cách hoạt động của các lời hứa, câu lệnh đó promise.then(result => { return result; })chính xác tương đương với promise, do đó, lệnh gọi phương thức không làm gì cả và nên được loại bỏ để đơn giản hóa mã và tăng cường khả năng đọc - một tuyên bố hoàn toàn đúng.
Bergi

1

Lời hứa của bạn đang chờ xử lý, hãy hoàn thành nó trước

userToken.then(function(result){
console.log(result)
})

sau mã còn lại của bạn. Tất cả những gì mã này làm là .then()hoàn thành lời hứa của bạn và ghi lại kết quả cuối cùng trong biến kết quả & in kết quả trong bảng điều khiển. Hãy nhớ rằng, bạn không thể lưu trữ kết quả trong biến toàn cục. Hy vọng rằng lời giải thích có thể giúp bạn.

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.