14

I have an es6 class, with an init() method responsible for fetching data, transforming it, then update the class's property this.data with newly transformed data. So far so good. The class itself has another getPostById() method, to just do what it sounds like. Here is the code for the class:

class Posts {
  constructor(url) {
    this.ready = false
    this.data = {}
    this.url = url
  }
  async init() {
      try { 
        let res = await fetch( this.url )
        if (res.ok) {
            let data = await res.json()

          // Do bunch of transformation stuff here

          this.data = data
          this.ready = true
            return data
        }
      } 
      catch (e) { 
         console.log(e)
      }
  }
  getPostById(id){
     return this.data.find( p => p.id === id )
  }
}  

Straightforward, except I have an async/await mechanism in the init() method. Now, this code will work correctly:

let allPosts = new Posts('https://jsonplaceholder.typicode.com/posts')

allPosts.init()
        .then( d => console.log(allPosts.getPostById(4)) )
// resulting Object correctly logged in console

but it only gets printed into the console: How could I use allPosts.getPostById(4) as a return of a function ?

Like:

let myFunc = async () => {
   const postId = 4
   await allPosts.init()  // I need to wait for this to finish before returning

   // This is logging correct value
   console.log( 'logging: ' + JSON.stringify(allPosts.getPostById( postId ), null, 4) )

   // How can I return the RESULT of allPosts.getPostById( postId ) ???
   return allPosts.getPostById( postId )
}

myFunc() returns a Promise but not the final value. I have read several related posts on the subject but they all give example of logging, never returning.

Here is a fiddle that includes two ways of handling init(): using Promise and using async/await. No matter what I try, I can't manage to USE the FINAL VALUE of getPostById(id).

The question of this post is: how can I create a function that will RETURN the VALUE of getPostById(id) ?

EDIT:

A lot of good answers trying to explain what Promises are in regards to the main execution loop. After a lot of videos and other good reads, here is what I understand now:

my function init() correctly returns. However, within the main event loop: it returns a Promise, then it is my job to catch the result of this Promise from within a kinda parallel loop (not a new real thread). In order to catch the result from the parallel loop there are two ways:

  1. use .then( value => doSomethingWithMy(value) )

  2. use let value = await myAsyncFn(). Now here is the foolish hiccup:

await can only be used within an async function :p

thus itself returning a Promise, usable with await which should be embed in an async function, which will be usable with await etc...

This means we cannot really WAIT for a Promise: instead we should catch parallel loop indefinitely: using .then() or async/await.

Thanks for the help !

Jona Rodrigues
  • 992
  • 1
  • 11
  • 23
  • 3
    see: https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call Basically, you can't. You either wait for the promise, or you use `await` in an async function. – Mark Dec 05 '17 at 23:58
  • Are you able to see `allPosts.data` when you console.log after `await allPosts`? – Nandu Kalidindi Dec 06 '17 at 00:04
  • @NanduKalidindi yes `console.log(allPosts.data)` prints correct values to the console. But still, enable to return it. @Mark_M yeah I read this but the question is not really answered I am afraid: it just embeds a course on asynchronous code... I need to get a bit more practical. If you tell me it is not possible: why on earth Promises exists if we can't use their result ? Ahaha – Jona Rodrigues Dec 06 '17 at 00:12
  • Or just do `await myFunc()` from where you are calling it, that should return your `getPostById` result. – Nandu Kalidindi Dec 06 '17 at 00:22
  • Promise is just a wrapper, it does not alter the fact that JavaScript code runs on a single thread. The async code can not complete until the caller has not completed. That is why the top-level, synchronous caller never sees the result (only an async function can await for an other async function). Everything you want to do with/after the result, should simply go into the 'then' block. – tevemadar Dec 06 '17 at 00:33
  • @JonaRodrigues you can use the result of promises just fine — you just need to get that value in the then() function. – Mark Dec 06 '17 at 00:42
  • @Mark_M ok but that would be a pretty limited use. If I understand well, instead of callback hell we should have HIgher Order Component hell where all my code would be wrapped in a `.then()` ? I can't really imagine that... – Jona Rodrigues Dec 06 '17 at 00:46
  • @JonaRodrigues I think the success of Node and javascript suggest it's not really a problem. Most people don't wrap everything in then()s -- they write functions. So you get what you need from your promise.then() pass it to the function that's expecting the data. It does take some getting used to if your coming from a language that isn't as intensely async driven. – Mark Dec 06 '17 at 01:40
  • @Mark_M Thanks, I am definitely open to any solution that would lead to have a function with result of my async function as output ! Could you show an example ? – Jona Rodrigues Dec 06 '17 at 01:59
  • 1
    I'm not sure you understand promises, you can't return a value because the value depends on something that is not available yet when your function is called. That is why you can use callbacks and promises, the function has to return something so it returns a promise of a value that may resolve or reject. The `async await` syntax suggests that the function waits but in fact it doesn't, `async` functions always return a promise. – HMR Dec 06 '17 at 03:46
  • "The async await syntax suggests that the function waits but in fact it doesn't" This is the correct answer to me. I just realized that after like 10 videos and 30 posts saying that "await" PAUSES script... when in fact it does NOT at all !!! :( – Jona Rodrigues Dec 06 '17 at 05:41
  • Hope you like reading, added an answer. There is a link to a very helpful video in the answer. Sorry for all your troubles trying to make sense of JS async stuff, we all have/had to go through that. – HMR Dec 06 '17 at 15:56
  • 1
    @HMR I'd say the function code that uses the `await` does indeed wait, but the *caller* of the function won't. (It has to use `await` (or `then`) itself for that). – Bergi Dec 06 '17 at 16:15
  • @Bergi Well, yea and no. Any `async` function will immediately return a promise that will resolve to any value you `return`. The `await` will create a promise and assign the resolve to something if you do `const res = await somePromiseFn()` but many people think await will actually cause the async function to wait returning something, this is not the case; as stated before; it will return a promise as soon as possible. urgh, another reason I'd prefer to leave the `async await` until promises are understood. I can't even explain it properly to you. – HMR Dec 06 '17 at 16:26
  • @HMR Luckily you don't need to explain it to me :-) Yes, it doesn't wait to return something (it waits to resolve the returned promise), but it does indeed pause the async code. – Bergi Dec 06 '17 at 17:52

3 Answers3

12

As for your comment; I'll add it as answer.

The code you write in JavaScript is run on one thread, that means that if your code could actually wait for something it will block any of your other code from getting executed. The event loop of JavaScript is explained very well in this video and if you like to read in this page.

A good example of blocking code in the browser is alert("cannot do anything until you click ok");. Alert blocks everything, the user can't even scroll or click on anything in the page and your code also blocks from executing.

Promise.resolve(22)
.then(x=>alert("blocking")||"Hello World")
.then(
  x=>console.log(
    "does not resolve untill you click ok on the alert:",
    x
  )
);

Run that in a console and you see what I mean by blocking.

This creates a problem when you want to do something that takes time. In other frameworks you'd use a thread or processes but there is no such thing in JavaScript (technically there is with web worker and fork in node but that's another story and usually far more complicated than using async api's).

So when you want to make a http request you can use fetch but fetch takes some time to finish and your function should not block (has to return something as fast as possible). This is why fetch returns a promise.

Note that fetch is implemented by browser/node and does run in another thread, only code you write runs in one thread so starting a lot of promises that only run code you write will not speed up anything but calling native async api's in parallel will.

Before promises async code used callbacks or would return an observable object (like XmlHttpRequest) but let's cover promises since you can convert the more traditional code to a promise anyway.

A promise is an object that has a then function (and a bunch of stuff that is sugar for then but does the same), this function takes 2 parameters.

  1. Resolve handler: A function that will be called by the promise when the promise resolves (has no errors and is finished). The function will be passed one argument with the resolve value (for http requests this usually is the response).
  2. Reject handler: A function that will be called by the promise when the promise rejects (has an error). This function will be passed one argument, this is usually the error or reason for rejection (can be a string, number or anything).

Converting callback to promise.

The traditional api's (especially nodejs api's) use callbacks:

traditionalApi(
  arg
  ,function callback(err,value){ 
    err ? handleFail(err) : processValue(value);
  }
);

This makes it difficult for the programmer to catch errors or handle the return value in a linear way (from top to bottom). It gets even more impossible to try and do things parallel or throttled parallel with error handling (impossible to read).

You can convert traditional api's to promises with new Promise

const apiAsPromise = arg =>
  new Promise(
    (resolve,reject)=>
      traditionalApi(
        arg,
        (err,val) => (err) ? reject(err) : resolve(val)
      )
  )

async await

This is what's called syntax sugar for promises. It makes promise consuming functions look more traditional and easier to read. That is if you like to write traditional code, I would argue that composing small functions is much easier to read. For example, can you guess what this does?:

const handleSearch = search =>
  compose([
    showLoading,
    makeSearchRequest,
    processRespose,
    hideLoading
  ])(search)
  .then(
    undefined,//don't care about the resolve
    compose([
      showError,
      hideLoading
    ])
  );

Anayway; enough ranting. The important part is to understand that async await doesn't actually start another thread, async functions always return a promise and await doesn't actually block or wait. It's syntax sugar for someFn().then(result=>...,error=>...) and looks like:

async someMethod = () =>
  //syntax sugar for:
  //return someFn().then(result=>...,error=>...)
  try{
    const result = await someFn();
    ...
   }catch(error){
     ...
   }
}

The examples allways show try catch but you don't need to do that, for example:

var alwaysReject = async () => { throw "Always returns rejected promise"; };
alwaysReject()
.then(
  x=>console.log("never happens, doesn't resolve")
  ,err=>console.warn("got rejected:",err)
);

Any error thrown or await returning a rejected promise will cause the async function to return a rejected promise (unless you try and catch it). Many times it is desirable to just let it fail and have the caller handle errors.

Catching errors could be needed when you want the promise to succeed with a special value for rejected promises so you can handle it later but the promise does not technically reject so will always resolve.

An example is Promise.all, this takes an array of promises and returns a new promise that resolves to an array of resolved values or reject when any one of them rejects. You may just want to get the results of all promises back and filter out the rejected ones:

const Fail = function(details){this.details=details;},
isFail = item => (item && item.constructor)===Fail;
Promise.all(
  urls.map(//map array of urls to array of promises that don't reject
    url =>
      fetch(url)
      .then(
        undefined,//do not handle resolve yet
        //when you handle the reject this ".then" will return
        //  a promise that RESOLVES to the value returned below (new Fail([url,err]))
        err=>new Fail([url,err])
      )
  )
)
.then(
  responses => {
    console.log("failed requests:");
    console.log(
      responses.filter(//only Fail type
        isFail
      )
    );
    console.log("resolved requests:");
    console.log(
      responses.filter(//anything not Fail type
        response=>!isFail(response)
      )
    );
  }
);
HMR
  • 37,593
  • 24
  • 91
  • 160
3

Your question and the comments suggest you could use a little intuition nudge about the way the event loop works. It really is confusing at first, but after a while it becomes second nature.

Rather than thinking about the FINAL VALUE, think about the fact that you have a single thread and you can't stop it — so you want the FUTURE VALUE -- the value on the next or some future event loop. Everything you write that is not asynchronous is going to happen almost immediately — functions return with some value or undefined immediately. There's nothing you can do about. When you need something asynchronously, you need to setup a system that is ready to deal with the async values when they return sometime in the future. This is what events, callbacks, promises (and async/await) all try to help with. If some data is asynchronous, you simply can not use it in the same event loop.

So what do you do?

If you want a pattern where you create an instance, call init() and then some function that further process it, you simply need to setup a system that does the processing when the data arrives. There are a lot of ways to do this. Here's one way that's a variation on your class:

function someAsync() {
  console.log("someAsync called")
  return new Promise(resolve => {
    setTimeout(() => resolve(Math.random()), 1000)
  })
}

class Posts {
  constructor(url) {
    this.ready = false
    this.data = "uninitilized"
    this.url = url
  }
  init() {
    this.data = someAsync()

  }
  time100() {
    // it's important to return the promise here
    return this.data.then(d => d * 100)
  }
}

let p = new Posts()
p.init()
processData(p)
// called twice to illustrate point
processData(p)

async function processData(posts) {
  let p = await posts.time100()
  console.log("randomin * 100:", p)
}

init() saves the promise returned from someAsync(). someAsync() could be anything that returns a promise. It saves the promise in an instance property. Now you can call then() or use async/await to get the value. It will either immediately return the value if the promise has already resolved or it will deal with it when it has resolved. I called processData(p) twice just to illustrate that it doesn't calle the someAsync() twice.

That's just one pattern. There are a lot more — using events, observables, just using then() directly, or even callbacks which are unfashionable, but still can be useful.

Community
  • 1
  • 1
Mark
  • 90,562
  • 7
  • 108
  • 148
  • Thanks a lot for the time taken here: really appreciated. I guess my problem comes from the understanding of async/await. In [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) it is clearly stated "await PAUSES execution"... To me it sounds like functions should be paused, wait for the object to be fetched, enclose the result in a variable if we need, then we can use this variable in the main loop !! PAUSE = PAUSE... it should PAUSE the loop and that's it. At least this is how I see it and why it causes me trouble. – Jona Rodrigues Dec 06 '17 at 05:24
  • I agree — that’s a confusing way to describe it. – Mark Dec 06 '17 at 05:28
  • @Mark_M Waooo, excellent answer dear photographer, the best ! – robe007 Jan 17 '18 at 19:34
1

NOTE: Wherever you use await it has to be inside an async function.

Check out the UPDATED FIDDLE

You need to use await myFunc() to get the value you expect from getPostById because an async function always returns a promise.

This sometimes is very frustrating as the whole chain needs to be converted into async functions but that's the price you pay for converting it to a synchronous code, I guess. I am not sure if that can be avoided but am interested in hearing from people who have more experience on this.

Try out the below code in your console by copying over the functions and then accessing final and await final.

NOTE:

An async function CAN contain an await expression. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

There is no rule that is must have await in order to even declare an async function. The example below uses an async function without await just to show that an async function always returns a promise.

const sample = async () => {
  return 100;
}

// sample() WILL RETURN A PROMISE AND NOT 100
// await sample() WILL RETURN 100

const init = async (num) => {
  return new Promise((resolve, reject) => {
    resolve(num);
  });
}

const myFunc = async (num) => {
  const k = await init(num);
  return k;
}

// const final = myFunc();
// final; This returns a promise
// await final; This returns the number you provided to myFunc
Nandu Kalidindi
  • 6,075
  • 1
  • 23
  • 36
  • 1
    You can't `await final` outside of an async function. – Mark Dec 06 '17 at 00:41
  • 1
    Yes, that is why I said the whole chain of functions needs to be converted into `async` functions. Also the reason why I suggested the above snippet in the web inspector just to get an idea. Well, if you did read that and downvoted, then cheers to you SIR! – Nandu Kalidindi Dec 06 '17 at 00:43
  • Sorry @NanduKalidindi did you see my [fiddle](https://jsfiddle.net/bb6ze668/3/) ? Correct me if I am wrong but it has exactly what you suggest and this is what is not working, isn't it ? – Jona Rodrigues Dec 06 '17 at 00:50
  • Please check the update fiddle in my answer. – Nandu Kalidindi Dec 06 '17 at 01:13
  • @NanduKalidindi thanks for your time and patience on the matter. I just saw your answer: I don't really know WHY on earth your answer have been downgraded like that !! Let me check the fiddle. – Jona Rodrigues Dec 06 '17 at 02:01
  • @NanduKalidindi Thanks for your updated fiddle. However, `showMyFunc` does not return the value. Rather it `alert`s it... I need to return the content of `allPosts.getPostsById`... – Jona Rodrigues Dec 06 '17 at 03:10
  • You don't need `async` in init function, you never use await. – HMR Dec 06 '17 at 03:42
  • @HMR I am just trying to show that an async function always returns a Promise! `An async function CAN contain an await expression`, There is no rule that it should have an `await`. – Nandu Kalidindi Dec 06 '17 at 15:01
  • 1
    Ok, but since you already return a promise there is no need for the async. You could do `const init = async num=>num` That will return a promise of `num` without the extra code. – HMR Dec 06 '17 at 16:35