275

My code:

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

And when i try to run something like this:

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

I'm getting:

Promise { <pending> }

But why?

My main goal is to get token from google.login(data.username, data.password) which returns a promise, into a variable. And only then preform some actions.

JLRishe
  • 99,490
  • 19
  • 131
  • 169
Src
  • 5,252
  • 5
  • 28
  • 56
  • 2
    @LoïcFaure-Lacroix , see this article: https://medium.com/@bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8#.w234uo7h3 – Src Aug 10 '16 at 22:30
  • @LoïcFaure-Lacroix look at `getFirstUser` function – Src Aug 10 '16 at 22:30
  • So what about it? It's a function returning a promise. – Loïc Faure-Lacroix Aug 10 '16 at 22:46
  • 1
    @LoïcFaure-Lacroix so you mean even in that example we need to use then to access the data promise returning in getFirstUser function? – Src Aug 10 '16 at 22:48
  • 1
    In that exemple yes, the only other way is to use the ES7 syntax "await" that seems to resolve stop the execution of the current context to await the result of the promise. If you read the article you'll see it. But since ES7 is probably almost supported nowhere yet, yes. The "then" is pretty much it. – Loïc Faure-Lacroix Aug 10 '16 at 22:50
  • @LoïcFaure-Lacroix Thank you. I know about await, but since i want to run script without babel, i decided to use Promises to avoid callback hell. But it seems to me, that i'm close to 'then hell' :D ... so, there are 0 way to use promises and get result of a promise into a variable and only then continue execution? – Src Aug 10 '16 at 23:12
  • No there is no way as far as I know as code is executed in a different context. – Loïc Faure-Lacroix Aug 10 '16 at 23:23
  • Technically, even with `await` it only pauses the execution inside a function (co-routine style). You can't await outside a function and the function you await in you need to mark as `async` and that function will return a promise instead. In the end, you cannot escape using callbacks when dealing with async code. – slebetman Aug 11 '16 at 05:10
  • @slebetman async/await allows you to get return value of a promise and place it in the variable. That's how you escaping callback. – Src Aug 12 '16 at 16:31
  • @Src: Yes, but only inside the async function. If you need the value in the main loop of your program you still need to pass a callback to the promise that the async function returns. – slebetman Aug 13 '16 at 04:26

9 Answers9

379

The promise will always log pending as long as its results are not resolved yet. You must call .then on the promise to capture the results regardless of the promise state (resolved or still pending):

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

Why is that?

Promises are forward direction only; You can only resolve them once. The resolved value of a Promise is passed to its .then or .catch methods.

Details

According to the Promises/A+ spec:

The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x). If x is a thenable, it attempts to make promise adopt the state of x, under the assumption that x behaves at least somewhat like a promise. Otherwise, it fulfills promise with the value x.

This treatment of thenables allows promise implementations to interoperate, as long as they expose a Promises/A+-compliant then method. It also allows Promises/A+ implementations to “assimilate” nonconformant implementations with reasonable then methods.

This spec is a little hard to parse, so let's break it down. The rule is:

If the function in the .then handler returns a value, then the Promise resolves with that value. If the handler returns another Promise, then the original Promise resolves with the resolved value of the chained Promise. The next .then handler will always contain the resolved value of the chained promise returned in the preceding .then.

The way it actually works is described below in more detail:

1. The return of the .then function will be the resolved value of the promise.

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. If the .then function returns a Promise, then the resolved value of that chained promise is passed to the following .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
  • 10,358
  • 4
  • 31
  • 52
  • Your first one is not working. `Uncaught SyntaxError: Unexpected token .`. The second one needs a return for `Promise` – zamil May 02 '17 at 15:15
  • @zamil you have to invoke the function, like in the second example. you cant `.then` on a uninvoked function. updated the answer – Bamieh May 04 '17 at 16:28
  • 4
    I'm bookmarking this so that I can keep it **forever.** I have been working a VERY long time to find truly clear and readable rules of how to actually build promises. Your blockquote of Promises/A+ spec is a perfect example of why it's been a PITA to self teach promises. It's also the ONLY time I've seen setTimeout used where it didn't confuse the lesson itself. And excellent reference, thank you. – monsto Apr 02 '20 at 11:46
  • 3
    it does not work while assigning the result to another variable. – Dipanshu Mahla Aug 27 '20 at 17:28
  • 2
    it still returns Promise { } – Dipanshu Mahla Aug 27 '20 at 17:28
65

I ran into the same issue and the answer for the problem is since ES2017, that you can simply await the functions return value (as of now, only works in async functions), like:

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

let userToken = await AuthUser(data)
console.log(userToken) // your data
halfer
  • 19,824
  • 17
  • 99
  • 186
Marius Seack
  • 799
  • 5
  • 6
  • 3
    You don't need the `.then(token => return token)`, that's just an unnecessary passthrough. Simply return the the google login call. – Soviut May 21 '19 at 20:26
  • This answer is unrelated to the question. The problem of the original poster has nothing to do with ES6' async/await. Promises existed before this new syntactic sugar was introduced in ECMAScript 2017 and they used Promises "under the hood". See [MDN on async/await](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await). – try-catch-finally Sep 27 '19 at 06:40
  • 4
    For ES8 / Nodejs, errors are thrown if you use `await` outside of an async function. Perhaps the better example here would be to make the `AuthUser` function `async`, that then ends with `return await google.login(...);` – Jon L. Oct 02 '19 at 13:33
  • 1
    This worked for me and is a simpler solution imo. Don't forget to use async. – Josh Sep 11 '20 at 07:21
  • 3
    It gives me this error ``` let answer = await getAnswer(url); ^^^^^ SyntaxError: await is only valid in async functions and the top level bodies of modules``` – Anshuman Kumar Dec 15 '20 at 15:33
  • @AnshumanKumar try like this: `const AuthUser = async(data) => { await google.login(data.username, data.password)}` – Simon Orro Feb 04 '22 at 11:25
6

If that situation happens for a multiple values like an array.

[ 
  Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> }
]

You can use Promise.all() this will resolve all promises.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Hasan Tezcan
  • 1,116
  • 1
  • 11
  • 23
5

The then method returns a pending promise which can be resolved asynchronously by the return value of a result handler registered in the call to then, or rejected by throwing an error inside the handler called.

So calling AuthUser will not suddenly log the user in synchronously, but returns a promise whose then registered handlers will be called after the login succeeds ( or fails). I would suggest triggering all login processing by a then clause of the login promise. E.G. using named functions to highlight the sequence of flow:

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);
}
traktor
  • 17,588
  • 4
  • 32
  • 53
2

See the MDN section on Promises. In particular, look at the return type of then().

To log in, the user-agent has to submit a request to the server and wait to receive a response. Since making your application totally stop execution during a request round-trip usually makes for a bad user experience, practically every JS function that logs you in (or performs any other form of server interaction) will use a Promise, or something very much like it, to deliver results asynchronously.

Now, also notice that return statements are always evaluated in the context of the function they appear in. So when you wrote:

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

the statement return token; meant that the anonymous function being passed into then() should return the token, not that the AuthUser function should. What AuthUser returns is the result of calling google.login(username, password).then(callback);, which happens to be a Promise.

Ultimately your callback token => { return token; } does nothing; instead, your input to then() needs to be a function that actually handles the token in some way.

Jesse Amano
  • 800
  • 5
  • 16
  • @Src I wrote my answer before the asker clarified that they were looking for a way to *synchronously* return a value, and without making assumptions about their dev environment or language version beyond what could be inferred by the code snippet -- that is, it's safe to assume ES6, but not necessarily ES7. – Jesse Amano Aug 10 '16 at 22:56
  • @AhmadBamieh All right, will do. I'm assuming the problem is that I've misunderstood how `return` is treated with the new(ish) closure syntax, in which case -- well, I strongly disapprove of that, but the error is still mine and I apologize for it. – Jesse Amano Aug 10 '16 at 22:58
  • i can understand how you feel as return should be sync, and it should return control to its caller, which is an anonymous function. but the explanation for you is that you should note that the return type of then() is a new Promise object, hence the value returned by the callback is used to fulfill or reject the promise. But, if the callback returns another Promise, then the new Promise (the one returned by then()) will be fulfilled only when this Promise fulfills. – Bamieh Aug 10 '16 at 23:01
  • 2
    @AhmadBamieh Er, I actually did know that part, which is why I asserted that `token => { return token; }` *does nothing* as opposed to claiming it was counterproductive. You can say `google.login(username, password).then(token=>{return token;}).then(token=>{return token;})` and so forth forever, but you'll only achieve returning a `Promise` that resolves with a token—same as if you just left it as `google.login(username, password);`. I'm not sure why you feel that this is "very wrong". – Jesse Amano Aug 10 '16 at 23:05
  • 1
    @AhmadBamieh: can you be more specific about what is wrong in this piece of text? I don't see anything, he just explains why `return token` doesn't work as the OP probably expected. – Bergi Aug 11 '16 at 04:03
  • @Bergi I updated my answer in this post to contain full details about the issue, comment section is way too small for code writing. please read it here http://stackoverflow.com/a/38884856/5384679 – Bamieh Aug 11 '16 at 09:42
  • 3
    @AhmadBamieh: there is indeed a misunderstanding. We all three know well how promises work, the statement is that `promise.then(result => { return result; })` is exactly equivalent to `promise`, therefore the method call *does nothing* and should be dropped to simplify the code and enhance readability - a statement that is completely true. – Bergi Aug 11 '16 at 10:07
  • @bergi aha, okay i understand your point now. this is correct – Bamieh Aug 11 '16 at 10:33
0

Your Promise is pending, complete it by

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

after your remaining code. All this code does is that .then() completes your promise & captures the end result in result variable & print result in console. Keep in mind, you cannot store the result in global variable. Hope that explanation might help you.

Christopher Nolan
  • 930
  • 1
  • 11
  • 15
0

I had the same issue earlier, but my situation was a bit different in the front-end. I'll share my scenario anyway, maybe someone might find it useful.

I had an api call to /api/user/register in the frontend with email, password and username as request body. On submitting the form(register form), a handler function is called which initiates the fetch call to /api/user/register. I used the event.preventDefault() in the beginning line of this handler function, all other lines,like forming the request body as well the fetch call was written after the event.preventDefault(). This returned a pending promise.

But when I put the request body formation code above the event.preventDefault(), it returned the real promise. Like this:

event.preventDefault();
    const data = {
        'email': email,
        'password': password
    }
    fetch(...)
     ...

instead of :

     const data = {
            'email': email,
            'password': password
        }
     event.preventDefault();
     fetch(...)
     ...
Adwaith
  • 1,099
  • 1
  • 9
  • 6
0

Try this

var number1 = document.getElementById("number1");
var number2 = document.getElementById("number2");
startAsync.addEventListener("click", function() {
    if (number1.value > 0 && number2.value > 0) {
        asyncTest(parseInt(number1.value), parseInt(number2.value)).then(function(result) {
            document.getElementById("promiseResolved").textContent = "promiseResolved: " + result
        });
    } else {
        asyncTest(1, 2).then(function(result) {
            document.getElementById("promiseResolved").textContent = "promiseResolved: " + result
        });
    }

});

async function asyncTest(a, b) {
    return await (a + b);
};
  <button id="startAsync">start Async function</button><br />
  <input type="number" id="number1" /><br />
  <input type="number" id="number2" /><br />
  <span id="promiseResolved"></span><br />
AllanRibas
  • 678
  • 5
  • 14
  • Thank you for your contribution, however this would be more useful if you added an explanation of how your solution works and why this is better/ how it compares to solutions already offered. – Eric Hepperle - CodeSlayer2010 Jan 25 '23 at 14:23
0

Im my case (JS) I forgot to add await

Naor Yael
  • 69
  • 4
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 03 '22 at 16:29