6

Before I begin, I acknowledge that there are several questions on SO which may sound similar to mine going by the title, however, all of them that I read are more complicated than my code and the explanation does not seem to pertain to my situation.

Can someone please help me understand what is going on in my code (snippet below) that is resulting in this error:

Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules.

As far as I can see, the await which is giving rise to the error is in a "top level" body. Or is something else meant by top level body? Thanks a lot!

EDIT for differentiating from the other suggested (similar) question here: My question does not deal with httpGet, some other context is different, and most importantly I have received an answer that solves the problem for me, unlike the suggestion given in the (solitary) answer to the other question. Hence, while I have been able to find a solution here, I believe it will be valuable for the general audience for my question to stay.

var data;
await getData();
document.body.write(data);

async function getData() {
    const res = await fetch("https://jsonplaceholder.typicode.com/posts", {
        method: 'GET',
        headers: {
          'Accept': 'application/json, text/plain, */*',
          'Content-type': 'application/json'
        }
    });
    data = await res.json();
}
SJB
  • 117
  • 1
  • 2
  • 11
  • 6
    *of modules* — This probably isn't a module, but simply global code? – deceze May 25 '21 at 13:06
  • 1
    It certainly isn't a module in a stack snippet. – Quentin May 25 '21 at 13:08
  • 1
    is it necessary to wrap the fetch call inside getData() method? – Vivek Bani May 25 '21 at 13:08
  • Yes, it is global code - in my script.js file. And I thought what could be more "top-level" than that? Can you please help point out the discrepancy? – SJB May 25 '21 at 13:08
  • 1
    Does this answer your question? [How to resolve the Syntax error : await is only valid in async function?](https://stackoverflow.com/questions/56685338/how-to-resolve-the-syntax-error-await-is-only-valid-in-async-function) – Heretic Monkey May 25 '21 at 13:11
  • `'Content-type': 'application/json'` declares that the body of the request is JSON. It's a GET request with **no** body at all… so that doesn't make sense. – Quentin May 25 '21 at 13:13
  • @Quentin That's a fair point, however it's only some simplified code I was using as an example and which is different from my actual code, so a few things may look incongruous. I hope it has no bearing on the substance of my question? – SJB May 25 '21 at 13:43
  • 1
    It doesn't, hence it being a comment and not part of my answer. – Quentin May 25 '21 at 13:43
  • @Rajdeep Debnath Strictly speaking it is not, but then if I directly make the fetch call in global code it will still require an await, correct? And then it will again lead to the same issue? – SJB May 25 '21 at 13:44
  • HereticMonkey Not exactly. I have instead received an answer from @Quentin on this page that works for me. – SJB May 25 '21 at 13:49
  • @SJB, actually you do not need await as fetch will return promise which can be handled by chaining then() and catch() – Vivek Bani May 25 '21 at 15:37
  • @RajdeepDebnath So, all the code inside the then() executes asynchronously, correct? That is, everything following the then() block will get executed possibly before the fetch call completes? Because that's what I am trying to avoid. – SJB May 27 '21 at 13:00
  • 1
    @sjb as per your question you have only 1 line `document.body.write(data)` and as this is dependent on fetch data should be inside `then()`, that's it. – Vivek Bani May 27 '21 at 13:07
  • @RajdeepDebnath Appreciate your answer. In the question I have only written simplified code that captures the problem. There is actually a fair bit of code following the fetch call. It probably could technically work if I put everything inside the `then()`, but that's not the kind of design I had in mind and I don't think it will be scalable, apart from looking clumsy (an example of [code smell](https://en.wikipedia.org/wiki/Code_smell), if you will). Let me know if you think differently. Cheers. – SJB May 27 '21 at 14:40
  • 1
    @SJB with `type=module` you can directly call fetch with await – Vivek Bani May 27 '21 at 16:58
  • @RajdeepDebnath Yes, that's the answer given by @Quentin below, which I accepted. Incidentally, would you know any downsides of using `type="module"` when linking to the JavaScript file? That is, any situations where it would be disadvantageous/erroneous to use it? – SJB May 27 '21 at 17:25
  • @SJB `type="module"` is preferable and safer as strict mode is enabled by default, cors is enabled, top-level await etc. I would suggest this mate. – Vivek Bani May 27 '21 at 17:46

2 Answers2

11

The top-level await means that you are trying to use async/await syntax outside async function. The workaround is to create some function e.g. main and put the code in it.

async function main() {
  var data;
  await getData();
  document.body.write(data);
}

main();

One day top-level async/await will be supported and there is a proposal for it. Meanwhile you can use this babel plugin to use it https://babeljs.io/docs/en/babel-plugin-syntax-top-level-await without wrapper function like main.

Ayzrian
  • 2,279
  • 1
  • 7
  • 14
  • Thanks. So I just need to put a wrapper around it, that's all? What's the point/difference syntactically/logically speaking? – SJB May 25 '21 at 13:10
  • I mean the point is, that if I put a wrapper around it, then I will need to call it from another function - and I DO NOT want to call it asynchronously. I want the fetch call to complete and all of the data to be loaded before the execution moves on. Is there a way I can achieve that? – SJB May 25 '21 at 13:11
  • 1
    Well, putting `await` is just syntax sugar for Promises. It will wait for the promise to resolve and then will execute the code below, but because HTTP requests are async some other code also may be executed. So it should does what you need in your case, it will wait the `getData` execution and then `document.body.write` call will be executed. – Ayzrian May 25 '21 at 13:14
  • That's precisely the issue. If I don't put await then "some other code" gets executed before the data download completes and that causes unwanted behavior. – SJB May 25 '21 at 13:16
  • You can call the wrapper in the script and the wrapper will contain all the code that you have to execute, and because you put `await` the code will wait for async requests before doing other stuff. You can make HTTP requests `sync` if you want to remove async behaviour. – Ayzrian May 25 '21 at 13:21
  • Thanks again. (1) *"because you put `await`"* - but wouldn't that cause the same issue again? (2) *"You can make HTTP requests sync"* - that seems like something I could try and I was going to, however, (3) I used the `type = "module"` solution suggested by @Quentin and it seems to do the trick for me (await does not result in an error) – SJB May 25 '21 at 13:33
5

Yes, it is global code - in my script.js file. And I thought what could be more "top-level" than that?

As pointed out in a comment, the problem isn't "top level" it is "in a module".

Web browsers will load scripts as modules only if the type attribute says to do so:

<script type="module" src="script.js"></script>

This enables support for import (CORS permitting), makes the script load asynchronously, and stops the top level scope being global.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
  • Thanks! I tried adding the type="module" attribute and as of now it seems to have worked! The script stops and waits for the function call to complete, and then proceeds further. So there is a solution! I appreciate the other responses as well but apparently it can be done, unlike what they say. – SJB May 25 '21 at 13:24