Mengapa fungsi asinkron saya menampilkan Promise {<pending>}, bukan nilai?


128

Kode saya:

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

Dan ketika saya mencoba menjalankan sesuatu seperti ini:

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

Saya mendapatkan:

Promise { <pending> }

Tapi kenapa?

Tujuan utama saya adalah mendapatkan token google.login(data.username, data.password)yang mengembalikan janji, menjadi variabel. Dan baru kemudian melakukan beberapa tindakan.


1
@ LoïcFaure-Lacroix, lihat artikel ini: medium.com/@bluepnume/…
Src

@ LoïcFaure-Lacroix melihat getFirstUserfungsinya
Src

Jadi bagaimana dengan itu? Ini adalah fungsi yang mengembalikan sebuah janji.
Loïc Faure-Lacroix

1
@ LoïcFaure-Lacroix jadi maksud Anda bahkan dalam contoh itu kita perlu menggunakan kemudian untuk mengakses janji data kembali dalam fungsi getFirstUser?
Src

Dalam contoh itu ya, satu-satunya cara lain adalah menggunakan sintaks ES7 "await" yang tampaknya menyelesaikan menghentikan eksekusi konteks saat ini untuk menunggu hasil yang dijanjikan. Jika Anda membaca artikel tersebut, Anda akan melihatnya. Tapi karena ES7 mungkin hampir tidak didukung di mana pun, ya. "Maka" cukup banyak.
Loïc Faure-Lacroix

Jawaban:


175

Promise akan selalu masuk dalam log selama hasilnya belum diselesaikan. Anda harus .thenmemenuhi janji untuk menangkap hasil terlepas dari status janji (diselesaikan atau masih menunggu keputusan):

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"
})

Mengapa demikian?

Janji hanya mengarah ke depan; Anda hanya dapat menyelesaikannya satu kali. Nilai yang diselesaikan dari a Promisediteruskan ke metode .thenatau nya .catch.

Detail

Menurut spesifikasi Promises / A +:

Prosedur penyelesaian janji adalah operasi abstrak yang mengambil sebuah janji dan nilai sebagai masukan, yang kami nyatakan sebagai [[Selesaikan]] (janji, x). Jika x adalah a thenable, ia mencoba membuat promise mengadopsi status x, dengan asumsi bahwa x berperilaku setidaknya seperti sebuah promise. Jika tidak, itu memenuhi janji dengan nilai x.

Perlakuan terhadap tabel ini memungkinkan implementasi promise untuk beroperasi, selama mereka mengekspos metode Promises / A + -compliant then. Ini juga memungkinkan implementasi Promises / A + untuk "mengasimilasi" implementasi yang tidak sesuai dengan metode yang masuk akal.

Spesifikasi ini agak sulit diurai, jadi mari kita uraikan. Aturannya adalah:

Jika fungsi di .thenhandler mengembalikan nilai, maka Promisepenyelesaian dengan nilai itu. Jika handler mengembalikan yang lain Promise, maka yang asli Promisemenyelesaikan dengan nilai yang dirantai Promise. .thenPenangan berikutnya akan selalu berisi nilai yang diselesaikan dari janji berantai yang dikembalikan sebelumnya .then.

Cara kerjanya dijelaskan lebih rinci di bawah ini:

1. Kembalinya .thenfungsi akan menjadi nilai yang dijanjikan.

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. Jika .thenfungsi mengembalikan a Promise, maka nilai yang diselesaikan dari janji berantai tersebut akan diteruskan ke berikut ini .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"
  });

Yang pertama Anda tidak berfungsi. Uncaught SyntaxError: Unexpected token .. Yang kedua perlu dikembalikan untukPromise
zamil

@zamil Anda harus menjalankan fungsinya, seperti pada contoh kedua. Anda tidak dapat .thenmenggunakan fungsi yang tidak dipanggil. memperbarui jawabannya
Bamieh

1
Saya menandai ini sehingga saya bisa menyimpannya selamanya. Saya telah bekerja sangat lama untuk menemukan aturan yang benar-benar jelas dan dapat dibaca tentang bagaimana sebenarnya membangun janji. Blockquote Anda dari spesifikasi Promises / A + adalah contoh sempurna mengapa itu menjadi PITA untuk janji belajar sendiri. Ini juga HANYA saat saya melihat setTimeout digunakan di mana itu tidak membingungkan pelajaran itu sendiri. Dan referensi yang bagus sekali, terima kasih.
monsto

21

Saya tahu pertanyaan ini ditanyakan 2 tahun yang lalu, tetapi saya mengalami masalah yang sama dan jawaban untuk masalahnya adalah sejak ES6, bahwa Anda dapat dengan mudah awaitmengembalikan nilai fungsi, seperti:

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
Anda tidak membutuhkannya .then(token => return token), itu hanya passthrough yang tidak perlu. Cukup kembalikan panggilan masuk Google.
Soviut

Jawaban ini tidak ada hubungannya dengan pertanyaan. Masalah dari poster asli tidak ada hubungannya dengan ES6 'async / await. Janji sudah ada sebelum gula sintaksis baru ini diperkenalkan di ECMAScript 2017 dan menggunakan Janji "di balik terpal". Lihat MDN di async / await .
coba-tangkap-akhirnya

Untuk ES8 / Nodejs, error akan muncul jika Anda menggunakan di awaitluar fungsi async. Mungkin contoh yang lebih baik di sini adalah membuat AuthUserfungsinya async, yang kemudian diakhiri denganreturn await google.login(...);
Jon L.

4

The thenkembali metode janji yang tertunda yang dapat diselesaikan asynchronously dengan nilai kembali dari penangan hasil terdaftar dalam panggilan untukthen , atau ditolak dengan melemparkan kesalahan dalam penangan disebut.

Jadi, pemanggilan AuthUsertidak akan mendadak memasukkan pengguna secara sinkron, tetapi mengembalikan sebuah promise yang penangannya yang terdaftar akan dipanggil setelah login berhasil (atau gagal). Saya menyarankan untuk memicu semua proses masuk dengan thenklausul janji masuk. EG menggunakan fungsi bernama untuk menyoroti urutan aliran:

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

Lihat bagian MDN tentang Janji. Secara khusus, lihat jenis kembalian dari then ().

Untuk masuk, agen pengguna harus mengirimkan permintaan ke server dan menunggu untuk menerima tanggapan. Karena membuat aplikasi Anda benar-benar menghentikan eksekusi selama permintaan bolak-balik biasanya menghasilkan pengalaman pengguna yang buruk, hampir setiap fungsi JS yang memasukkan Anda (atau melakukan bentuk interaksi server lainnya) akan menggunakan Promise, atau sesuatu yang sangat mirip dengannya , untuk memberikan hasil secara asinkron.

Sekarang, perhatikan juga bahwa returnpernyataan selalu dievaluasi dalam konteks fungsi kemunculannya. Jadi ketika Anda menulis:

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

pernyataan itu return token;berarti bahwa fungsi anonim yang diteruskan then()harus mengembalikan token, bukan AuthUserfungsi yang seharusnya. Apa AuthUsermengembalikan adalah hasil dari panggilan google.login(username, password).then(callback);, yang terjadi menjadi Promise.

Pada akhirnya panggilan balik Anda token => { return token; }tidak melakukan apa pun; sebaliknya, masukan Anda then()ke harus berupa fungsi yang benar-benar menangani token dalam beberapa cara.


@Src Saya menulis jawaban saya sebelum penanya mengklarifikasi bahwa mereka mencari cara untuk mengembalikan nilai secara sinkron , dan tanpa membuat asumsi tentang lingkungan dev atau versi bahasa mereka di luar apa yang dapat disimpulkan oleh cuplikan kode - yaitu, aman untuk mengasumsikan ES6, tetapi tidak harus ES7.
Jesse Amano

@AhmadBamieh Baiklah, akan kulakukan. Saya berasumsi masalahnya adalah bahwa saya salah paham bagaimana returndiperlakukan dengan sintaksis penutupan (ish) yang baru, dalam hal ini - saya sangat tidak setuju, tetapi kesalahan itu masih milik saya dan saya minta maaf untuk itu.
Jesse Amano

2
@AhmadBamieh Er, saya sebenarnya tahu bagian itu, itulah mengapa saya menegaskan bahwa token => { return token; } tidak ada yang bertentangan dengan klaim itu kontraproduktif. Anda dapat mengatakan google.login(username, password).then(token=>{return token;}).then(token=>{return token;})dan seterusnya selamanya, tetapi Anda hanya akan mencapai pengembalian Promiseyang diselesaikan dengan token — sama seperti jika Anda membiarkannya begitu saja google.login(username, password);. Saya tidak yakin mengapa Anda merasa ini "sangat salah".
Jesse Amano

1
@AhmadBamieh: dapatkah Anda lebih spesifik tentang apa yang salah dalam teks ini? Saya tidak melihat apa-apa, dia hanya menjelaskan mengapa return tokentidak bekerja seperti yang diharapkan OP.
Bergi

3
@AhmadBamieh: memang ada kesalahpahaman. Kita ketiganya tahu betul bagaimana promise bekerja, pernyataannya promise.then(result => { return result; })persis sama dengan promise, oleh karena itu pemanggilan metode tidak melakukan apa pun dan harus dibuang untuk menyederhanakan kode dan meningkatkan keterbacaan - pernyataan yang sepenuhnya benar.
Bergi

1

Janji Anda menunggu, selesaikan dengan

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

setelah kode Anda yang tersisa. Semua yang dilakukan kode ini adalah yang .then()memenuhi janji Anda & menangkap hasil akhir dalam variabel hasil & hasil cetak di konsol. Perlu diingat, Anda tidak dapat menyimpan hasil dalam variabel global. Semoga penjelasan itu bisa membantu Anda.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.