Dlaczego moja funkcja asynchroniczna zwraca Promise {<pending>} zamiast wartości?

129

Mój kod:

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

A kiedy próbuję uruchomić coś takiego:

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

Otrzymuję:

Promise { <pending> }

Ale dlaczego?

Moim głównym celem jest uzyskanie tokena, z google.login(data.username, data.password)którego zwraca obietnicę, do zmiennej. I dopiero wtedy wykonaj pewne czynności.

Src
źródło
1
@ LoïcFaure-Lacroix, zobacz ten artykuł: medium.com/@bluepnume/…
Src
@ LoïcFaure-Lacroix spójrz na getFirstUserfunkcję
Src,
Więc co z tym? To funkcja zwracająca obietnicę.
Loïc Faure-Lacroix
1
@ LoïcFaure-Lacroix, więc masz na myśli, że nawet w tym przykładzie musimy użyć wtedy, aby uzyskać dostęp do obietnicy danych zwracanej w funkcji getFirstUser?
Src
W tym przykładzie tak, jedynym innym sposobem jest użycie składni ES7 „await”, która wydaje się rozwiązywać zatrzymanie wykonywania bieżącego kontekstu i oczekiwanie na wynik obietnicy. Jeśli przeczytasz artykuł, zobaczysz go. Ale ponieważ ES7 prawdopodobnie nie jest jeszcze nigdzie obsługiwany, tak. „Wtedy” to właściwie wszystko.
Loïc Faure-Lacroix

Odpowiedzi:

176

Obietnica będzie zawsze rejestrowana jako oczekująca, o ile jej wyniki nie zostaną jeszcze rozwiązane. Musisz odwołać .thensię do obietnicy, aby uchwycić wyniki niezależnie od stanu obietnicy (rozwiązany lub wciąż oczekujący):

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

Dlaczego?

Obietnice dotyczą tylko kierunku naprzód; Możesz je rozwiązać tylko raz. Rozstrzygnięta wartość a Promisejest przekazywana do jej metod .thenlub .catch.

Detale

Zgodnie ze specyfikacją Promises / A +:

Procedura rozstrzygania obietnic jest abstrakcyjną operacją przyjmującą jako dane wejściowe obietnicę i wartość, którą oznaczamy jako [[Rozwiąż]] (obietnica, x). Jeśli x jest zdolny do działania, to stara się, aby obietnica przyjęła stan x, przy założeniu, że x zachowuje się przynajmniej trochę jak obietnica. W przeciwnym razie spełnia obietnicę o wartości x.

Takie traktowanie elementów umożliwia współpracę obiecujących implementacji, o ile ujawniają one następnie metodę Promises / A +. Pozwala również implementacjom Promises / A + na „asymilację” niezgodnych implementacji z rozsądnymi metodami.

Ta specyfikacja jest trochę trudna do przeanalizowania, więc podzielmy ją. Zasada jest taka:

Jeśli funkcja w programie .thenobsługi zwraca wartość, to jest Promiserozwiązywana z tą wartością. Jeśli przewodnik zwróci inny Promise, oryginał zostanie Promiserozstrzygnięty z rozstrzygniętą wartością łańcucha Promise. Następny .thenprogram obsługi zawsze będzie zawierał rozstrzygniętą wartość połączonej obietnicy zwróconej w poprzednim .then.

Sposób, w jaki to faktycznie działa, opisano bardziej szczegółowo poniżej:

1. Zwrot .thenfunkcji będzie ustaloną wartością promesy.

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. Jeśli .thenfunkcja zwraca a Promise, to rozwiązana wartość tej połączonej obietnicy jest przekazywana do następującego .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"
  });
Bamieh
źródło
Twój pierwszy nie działa. Uncaught SyntaxError: Unexpected token .. Drugi potrzebuje powrotuPromise
zamil
@zamil musisz wywołać funkcję, jak w drugim przykładzie. nie możesz .thenpełnić funkcji, której nie wezwano. zaktualizował odpowiedź
Bamieh
1
Zapisuję to do zakładek, żeby móc to zachować na zawsze. BARDZO długo pracowałem, aby znaleźć naprawdę jasne i czytelne zasady, jak faktycznie budować obietnice. Twój cytat blokowy dotyczący obietnic / specyfikacji A + jest doskonałym przykładem tego, dlaczego samodzielne nauczanie obietnic było PITA. Jest to również JEDYNY raz, kiedy widziałem setTimeout, gdy nie pomylił on samej lekcji. Dziękuję bardzo.
monsto
21

Wiem, że to pytanie zostało zadane 2 lata temu, ale napotykam ten sam problem, a odpowiedź na problem jest taka, że ​​od ES6 można po prostu awaitfunkcje zwracać wartość, na przykład:

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
Marius Seack
źródło
3
Nie potrzebujesz .then(token => return token), to tylko niepotrzebne przejście. Po prostu oddzwoń na połączenie logowania Google.
Soviut
Ta odpowiedź nie ma związku z pytaniem. Problem z oryginalnym plakatem nie ma nic wspólnego z async / await w ES6. Obietnice istniały, zanim ten nowy cukier składniowy został wprowadzony w ECMAScript 2017 i używali Promises „pod maską”. Zobacz MDN na async / await .
spróbuj złapać wreszcie
W przypadku ES8 / Nodejs błędy są generowane, jeśli używasz awaitpoza funkcją asynchroniczną. Być może lepszym przykładem byłoby tutaj utworzenie AuthUserfunkcji async, która następnie kończy się nareturn await google.login(...);
Jon L.
4

thenMetoda zwraca oczekujące obietnicę, które mogą być rozwiązane w sposób asynchroniczny o wartości zwracanej handlera wynik zarejestrowany w wywołaniu thenlub odrzucony przez rzucanie błąd wewnątrz przewodnika nazwie.

Tak więc wywołanie AuthUsernie spowoduje nagłego zalogowania użytkownika synchronicznie, ale zwróci obietnicę, której następnie zarejestrowane procedury obsługi zostaną wywołane po pomyślnym (lub niepowodzeniu) logowania. Sugerowałbym wyzwalanie całego przetwarzania logowania thenklauzulą ​​obietnicy logowania. EG używając nazwanych funkcji do wyróżnienia sekwencji przepływu:

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);
}
traktor53
źródło
1

Zobacz sekcję MDN dotyczącą obietnic. W szczególności spójrz na zwracany typ then ().

Aby się zalogować, agent użytkownika musi wysłać żądanie do serwera i czekać na odpowiedź. Ponieważ całkowite wstrzymanie wykonywania aplikacji podczas przesyłania żądania w obie strony zwykle powoduje złe wrażenia użytkownika, praktycznie każda funkcja JS, która loguje Cię (lub wykonuje jakąkolwiek inną formę interakcji z serwerem), będzie używać Obietnicy lub czegoś bardzo podobnego , aby dostarczać wyniki asynchronicznie.

Zauważ też, że returninstrukcje są zawsze oceniane w kontekście funkcji, w której się pojawiają. Kiedy więc napisałeś:

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

instrukcja return token;oznaczała, że ​​przekazywana funkcja anonimowa then()powinna zwracać token, a nie AuthUserfunkcja powinna. To, co AuthUserwraca, jest wynikiem wezwania google.login(username, password).then(callback);, które okazuje się być obietnicą.

Ostatecznie twój callback token => { return token; }nic nie robi; zamiast tego dane wejściowe then()muszą być funkcją, która w jakiś sposób obsługuje token.

Jesse Amano
źródło
@Src Napisałem swoją odpowiedź, zanim pytający wyjaśnił, że szukali sposobu na synchroniczne zwrócenie wartości i bez robienia założeń dotyczących ich środowiska deweloperskiego lub wersji językowej wykraczających poza to, co można wywnioskować z fragmentu kodu - czyli jest to bezpieczne założyć ES6, ale niekoniecznie ES7.
Jesse Amano,
@AhmadBamieh W porządku. Zakładam, że problem polega na tym, że źle zrozumiałem, jak returntraktowana jest nowa (ish) składnia domknięcia, w którym to przypadku - cóż, zdecydowanie tego nie pochwalam, ale błąd nadal jest mój i przepraszam za niego.
Jesse Amano,
2
@AhmadBamieh Er, właściwie znałem tę część, dlatego stwierdziłem, że token => { return token; } nie robi nic w przeciwieństwie do twierdzenia, że przynosi efekt przeciwny do zamierzonego. Możesz mówić google.login(username, password).then(token=>{return token;}).then(token=>{return token;})i tak dalej w nieskończoność, ale uzyskasz tylko zwrócenie wyniku, Promisektóry zostanie rozpatrzony za pomocą żetonu - tak samo, jakbyś zostawił go jako google.login(username, password);. Nie jestem pewien, dlaczego uważasz, że jest to „bardzo złe”.
Jesse Amano,
1
@AhmadBamieh: czy możesz bardziej szczegółowo określić, co jest nie tak w tym fragmencie tekstu? Nic nie widzę, po prostu wyjaśnia, dlaczego return tokennie działa zgodnie z oczekiwaniami OP.
Bergi
3
@AhmadBamieh: rzeczywiście jest nieporozumienie. Wszyscy trzej wiemy dobrze, jak działają obietnice, stwierdzenie jest promise.then(result => { return result; })dokładnie równoważne z promise, dlatego wywołanie metody nic nie robi i należy je porzucić, aby uprościć kod i zwiększyć czytelność - stwierdzenie, które jest całkowicie prawdziwe.
Bergi
1

Twoja obietnica oczekuje na realizację, wypełnij ją do

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

po pozostałym kodzie. Wszystko, co robi ten kod, to .then()wypełnienie obietnicy i uchwycenie wyniku końcowego w postaci zmiennej wyniku i wyniku drukowania w konsoli. Pamiętaj, że nie możesz przechowywać wyniku w zmiennej globalnej. Mam nadzieję, że to wyjaśnienie może ci pomóc.

Naveen Nirban Yadav
źródło