Почему моя асинхронная функция возвращает Promise {<pending>} вместо значения?

129

Мой код:

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

И когда я пытаюсь запустить что-то вроде этого:

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

Я собираюсь:

Promise { <pending> }

Но почему?

Моя основная цель - получить токен, из google.login(data.username, data.password)которого возвращается обещание, в переменную. И только после этого выполните некоторые действия.

Src
источник
1
@ LoïcFaure-Lacroix, см. Эту статью: medium.com/@bluepnume/…
Src
@ LoïcFaure-Lacroix посмотрите на getFirstUserфункцию
Src
Так что насчет этого? Это функция, возвращающая обещание.
Лоик Фор-Лакруа,
1
@ LoïcFaure-Lacroix, значит, вы имеете в виду, что даже в этом примере нам нужно использовать then для доступа к обещанию данных, возвращаемому в функции getFirstUser?
Src
В этом примере да, единственный другой способ - использовать синтаксис ES7 «await», который, кажется, разрешает остановить выполнение текущего контекста, чтобы дождаться результата обещания. Если вы прочтете статью, то увидите это. Но поскольку ES7, вероятно, еще почти нигде не поддерживается, да. «Тогда» в значительной степени и есть.
Лоик Фор-Лакруа,

Ответы:

177

Обещание всегда будет записываться в состояние ожидания, пока его результаты еще не разрешены. Вы должны вызвать .thenобещание для захвата результатов независимо от состояния обещания (разрешено или все еще ожидает выполнения):

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

Это почему?

Обещания - это только прямое направление; Вы можете решить их только один раз. Разрешенное значение a Promiseпередается его.then или .catchметодам.

подробности

Согласно спецификации Promises / A +:

Процедура разрешения обещания - это абстрактная операция, принимающая в качестве входных данных обещание и значение, которое мы обозначаем как [[Resolve]] (обещание, x). Если x является пригодным для использования, он пытается заставить обещание принять состояние x, при условии, что x ведет себя, по крайней мере, в некоторой степени как обещание. В противном случае он выполняет обещание со значением x.

Такая обработка таблиц позволяет взаимодействовать реализациям обещаний при условии, что они предоставляют метод then, соответствующий Promises / A +. Это также позволяет реализациям Promises / A + «ассимилировать» несовместимые реализации с разумными тогда методами.

Эту спецификацию немного сложно разобрать, поэтому давайте разберемся с ней. Правило такое:

Если функция в .thenобработчике возвращает значение, то Promiseразрешается с этим значением. Если обработчик возвращает другой Promise, то исходный Promiseразрешается с разрешенным значением связанного Promise. Следующий .thenобработчик всегда будет содержать разрешенное значение связанного обещания, возвращенного в предыдущем .then.

Как это работает на самом деле, более подробно описано ниже:

1. Возврат .thenфункции будет разрешенным значением обещания.

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. Если .thenфункция возвращает a Promise, то разрешенное значение этого связанного обещания передается следующему .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
источник
Ваш первый не работает. Uncaught SyntaxError: Unexpected token ., Вторую нужно вернуть заPromise
замил 02
@zamil, вы должны вызвать функцию, как во втором примере. вы не можете .thenиспользовать невызванную функцию. обновил ответ
Bamieh
1
Я добавляю это в закладки, чтобы сохранить навсегда. Я ОЧЕНЬ долго работал, чтобы найти по-настоящему ясные и понятные правила того, как на самом деле строить обещания. Ваша цитата из Promises / A + spec - прекрасный пример того, почему самообучение обещаниям стало PITA. Это также ЕДИНСТВЕННЫЙ раз, когда я видел, как setTimeout не запутал сам урок. И отличная ссылка, спасибо.
пн,
21

Я знаю, что этот вопрос был задан 2 года назад, но я столкнулся с той же проблемой, и ответ на эту проблему с ES6, что вы можете просто awaitвозвращать значение функции, например:

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
Вам не нужен .then(token => return token), это просто ненужный переход. Просто верните вызов входа в Google.
Совьют
Этот ответ не имеет отношения к вопросу. Проблема оригинального плаката не имеет ничего общего с async / await в ES6. Обещания существовали до того, как этот новый синтаксический сахар был представлен в ECMAScript 2017, и они использовали обещания «под капотом». См. MDN по async / await .
try-catch-finally
Для ES8 / Nodejs возникают ошибки, если вы используете awaitвне асинхронной функции. Возможно, лучшим примером здесь было бы создание AuthUserфункции async, которая затем заканчивается наreturn await google.login(...);
Джон Л.
4

thenМетод возвращает ожидающее обещание , которое может быть решено асинхронно возвращаемым значением результирующего обработчика , зарегистрированным в вызове then, или отвергнут метание ошибки внутри обработчика вызывается.

Таким образом, вызов AuthUserне будет внезапно регистрировать пользователя в синхронном режиме, но возвращает обещание, зарегистрированные обработчики которого будут вызываться после успешного (или неудачного) входа в систему. Я бы предложил запускать всю обработку входа в систему с помощью thenпункта обещания входа в систему. EG использует именованные функции для выделения последовательности потока:

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
источник
1

См. Раздел MDN об обещаниях. В частности, посмотрите на возвращаемый тип then ().

Чтобы войти в систему, пользовательский агент должен отправить запрос на сервер и дождаться ответа. Поскольку полное прекращение выполнения вашего приложения во время передачи запроса туда и обратно обычно ухудшает взаимодействие с пользователем, практически каждая функция JS, которая выполняет вход в систему (или выполняет любую другую форму взаимодействия с сервером), будет использовать обещание или что-то очень похожее на него. , чтобы предоставлять результаты асинхронно.

Также обратите внимание, что returnоператоры всегда оцениваются в контексте функции, в которой они появляются. Итак, когда вы написали:

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

это утверждение return token;означало, что анонимная функция then()должна возвращать токен, а не AuthUserфункция. То, что AuthUserвозвращается, является результатом вызова google.login(username, password).then(callback);, который оказывается обещанием.

В конечном итоге ваш обратный вызов token => { return token; }ничего не делает; вместо этого ваш ввод then()должен быть функцией, которая действительно каким-то образом обрабатывает токен.

Джесси Амано
источник
@Src Я написал свой ответ до того, как спрашивающий пояснил, что они искали способ синхронного возврата значения, не делая предположений о своей среде разработки или языковой версии, выходящих за рамки того, что можно было бы сделать из фрагмента кода - то есть это безопасно предполагать ES6, но не обязательно ES7.
Джесси Амано,
@AhmadBamieh Хорошо, подойдет. Я предполагаю, что проблема в том, что я неправильно понял, как returnобрабатывается новый (ish) синтаксис закрытия, и в этом случае - ну, я категорически не одобряю этого, но ошибка все еще моя, и я извиняюсь за нее.
Джесси Амано,
2
@AhmadBamieh Эм, я действительно знал эту часть, поэтому я утверждал, что token => { return token; } она ничего не делает, а не утверждает, что она контрпродуктивна. Вы можете говорить google.login(username, password).then(token=>{return token;}).then(token=>{return token;})и так далее до бесконечности, но вы получите только возвращение, Promiseкоторое разрешается с помощью токена - так же, как если бы вы просто оставили его как google.login(username, password);. Я не уверен, почему вы считаете, что это «очень неправильно».
Джесси Амано,
1
@AhmadBamieh: не могли бы вы уточнить, что не так в этом фрагменте текста? Я ничего не вижу, он просто объясняет, почему return tokenне работает так, как, вероятно, ожидал OP.
Берги
3
@AhmadBamieh: действительно есть недоразумение. Мы все трое хорошо знаем, как работают обещания, это утверждение в promise.then(result => { return result; })точности эквивалентно promise, поэтому вызов метода ничего не делает, и его следует отбросить, чтобы упростить код и повысить удобочитаемость - утверждение, которое полностью верно.
Bergi
1

Ваше обещание ожидается, завершите его

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

после вашего оставшегося кода. Все, что делает этот код, это то, что .then()выполняет ваше обещание и фиксирует конечный результат в переменной результата и выводит результат в консоль. Имейте в виду, что вы не можете сохранить результат в глобальной переменной. Надеюсь, это объяснение поможет вам.

Навин Нирбан Ядав
источник