How to use await in a callback? - javascript

My code is:
async function run() {
await sleep(1);
fs.readFile('list.txt', 'utf8', function (err, text) {
console.log(text);
await sleep(5);
});
}
run();
await sleep(1) is fine, but await sleep(5) results in:
SyntaxError: async is only valid in async function
Is fs.readFile the problem? How do I use await in such a situation?
The above is a reduced example to test. In my actual usage, I need to put the await sleep in a callback deeply nested inside a few more asynchronous functions.

I'm not sure what sleep is doing, but the problem in your question is happening because the callback to fs.readfile is not an async function and the that is where await sleep(5) is.
async function run() {
await sleep(1);
fs.readFile('list.txt', 'utf8', function (err, text) {
console.log(text); /* ^this isn't an async function */
await sleep(5);
});
}
You could make that error go away by passing an async function as a callback, however mixing promise and callback flows is not recommended and leads to problems, especially when handling errors. It prevents you from chaining the promise and catching errors from the inner await outside of run().
A better approach is to wrap fs.readFile() in a promise and await that.
async function run() {
await sleep(1);
const text = await new Promise((resolve, reject) => {
fs.readFile('list.txt', 'utf8', function (err, text) {
if (err) reject(err) else resolve(text);
});
});
console.log(text);
await sleep(5);
}
This will allow you to catch any errors in much more robust way with:
run()
.then(() => /*...*/)
.catch(err => /* handle error */
and avoid unhandled rejections.

The above is a reduced example to test. In my actual usage, I need to put the await sleep in a callback deeply nested inside a few more asynchronous functions.
Callback based APIs shouldn't be augmented with async..await or promises in general when there is a chance to stick to promises (may not be possible if a callback is called more than once). This results in poor control flow and error handling.
Once there is async callback function inside run, it's impossible to chain nested promise with run().then(...). Errors from nested promises may remain unhandled as well and result in UnhandledPromiseRejectionWarning.
The proper way is to move from callbacks to promises and use promise control flow. Generally this can be achieved with promise constructor:
async function run() {
await sleep(1);
const text = await new Promise((resolve, reject) => {
fs.readFile('list.txt', 'utf8', function (err, text) {
if (err) reject(err) else resolve(text);
});
});
console.log(text);
await sleep(5);
}
There are numerous ways to get promises from Node callback based APIs, e.g. fs provides promise API since Node 10:
async function run() {
await sleep(1);
const text = await fs.promises.readFile('list.txt', 'utf8');
console.log(text);
await sleep(5);
}

If your function includes an "await", you must prefix your function declaration with "async".
NOTE: By making your function an "async" function a promise is always returned. So if you return a string, that string will get automatically wrapped in a Promise object.
You can therefore return a Promise manually (for clarity). A simple resolved promise is sufficient:
async function foo(){
await bar();
return Promise.resolve('optional'); // optionally resolve with a value
}
The keyword "async" always goes before the keyword "function":
var foo = async function(){
await bar();
return Promise.resolve('optional'); // optionally resolve with a value
}
... and well for arrow functions:
var foo = sally( async () => {
await bar();
return Promise.resolve('optional'); // optionally resolve with a value
})
To retrieve the return value:
foo().then(val => console.log(val) ); // prints "optional" to console

You have to use aysnc key before function start. await keyword
will not work with normal function.
async function run() {
await sleep(1);
fs.readFile('list.txt', 'utf8', async function (err, text) {
console.log(text);
await sleep(5);
});
}
run();

Related

how do i resolve promises

I read like through 10 pages on how to resolve promises but i still don't get it.
Info: I want to fetch a specific member of a discord server
Currently I have a async function with the promise inside that returns it but it gives me a message with "Invalid Body Form"
async function mbr() {
const mB = await client.guilds.cache.get("1037783624449282189").members.fetch(`${args[0]}`).then((m) => { return m; });
return mB
}
let member = mbr()
if (member.roles.cache.has("1039983830389510305"))
Edit: It gives me this Error when i do a async Function inside a async function
G:\Desktop\Minecraft Modding\Bedrock\_____\Server\legend\plugins\nodejs\discord-bot\node_modules\#discordjs\rest\dist\index.js:659
throw new DiscordAPIError(data, "code" in data ? data.code : data.error, status, method, url, requestData);
^
DiscordAPIError[50035]: Invalid Form Body
user_id[NUMBER_TYPE_COERCE]: Value "undefined" is not snowflake.
at SequentialHandler.runRequest (G:\Desktop\Minecraft Modding\Bedrock\_____\Server\legend\plugins\nodejs\discord-bot\node_modules\#discordjs\rest\dist\index.js:659:15)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async SequentialHandler.queueRequest (G:\Desktop\Minecraft Modding\Bedrock\_____\Server\legend\plugins\nodejs\discord-bot\node_modules\#discordjs\rest\dist\index.js:458:14)
at async REST.request (G:\Desktop\Minecraft Modding\Bedrock\_____\Server\legend\plugins\nodejs\discord-bot\node_modules\#discordjs\rest\dist\index.js:902:22)
at async GuildMemberManager._fetchSingle (G:\Desktop\Minecraft Modding\Bedrock\_____\Server\legend\plugins\nodejs\discord-bot\node_modules\discord.js\src\managers\GuildMemberManager.js:489:18)
at async mbr (G:\Desktop\Minecraft Modding\Bedrock\_____\Server\legend\plugins\nodejs\discord-bot\chatBridge\accountLink.js:8:32)
at async G:\Desktop\Minecraft Modding\Bedrock\_____\Server\legend\plugins\nodejs\discord-bot\chatBridge\accountLink.js:11:30 {
requestBody: { files: undefined, json: undefined },
rawError: {
code: 50035,
errors: {
user_id: {
_errors: [
{
code: 'NUMBER_TYPE_COERCE',
message: 'Value "undefined" is not snowflake.'
}
]
}
},
message: 'Invalid Form Body'
},
code: 50035,
status: 400,
method: 'GET',
url: 'https://discord.com/api/v10/guilds/1037783624449282189/members/undefined'
}
Promises need to be awaited, either by using p.then(callback), or by using await p.
For example, this Promise:
const p = new Promise(resolve => setTimeout(resolve("Hello world!"), 100));
will resolve in 100 ms, and can be used either like this
p.then(message => console.log(message));
or like this
console.log(await p);
In short, your function:
async function mbr() {
const mB = await client.guilds.cache.get("1037783624449282189").members.fetch(`${args[0]}`).then((m) => { return m; });
return mB
}
can be resolved by either
mbr().then(member => {
if (member.roles.cache.has("1039983830389510305")) { ... }
});
or
const member = await mbr();
if (member.roles.cache.has("1039983830389510305")) { ... }
Note that in order to use await, you must be in an async context! By default, Node.js's global scope is synchronous, so you need to wrap your code inside an anonymous async function, like so:
(async () => {
const member = await mbr();
if (member.roles.cache.has("1039983830389510305")) { ... }
})(); // <-- call immediately
You have three ways to accomplish this:
Top-level await
Note: If this is an option in your JavaScript runtime.
let member = await mbr();
if (member.roles.cache.has("1039983830389510305")) {
// ...
}
IIFE async
(async () => {
let member = await mbr();
if (member.roles.cache.has("1039983830389510305")) {
// ...
}
})();
Promise resolution
mbr()
.then((member) => {
if (member.roles.cache.has("1039983830389510305")) {
// ...
}
});
Also, your mbr function can be simplified to:
const mbr = async () =>
client.guilds.cache
.get("1037783624449282189").members
.fetch(`${args[0]}`)
async/await can be used to make the code for working with promises a lot more concise and readable.
In case you don't know how promises work, here's a quick explanation:
Promises are a way for developers to create asynchronous, non-blocking code that will run "at the same time" as the current code. All promises should eventually resolve or reject, which are callbacks that let developers run extra code after a promise is finished.
In Discord specifically, fetching members creates an api request, which could take any amount of time, so it returns a promise. In order to actually use the data from the promise, you have to retrieve the data after it's resolved, which can be easily accomplished with async/await.
// since this function is async, it will ALWAYS return a promise
async function mbr() {
// await can be used to pause code execution until the promise resolves
// you don't need to use `.then()`, because await is already resolving your promise
const mB = await client.guilds.cache.get("1037783624449282189").members.fetch(`${args[0]}`);
// return the resolved value of the promise
return mB
}
// you need to add await here, because mbr() now returns a promise itself
// make sure this code is placed in an async function or use top level awaits
let member = await mbr()
// you can now use the member object and the data it contains
if (member.roles.cache.has("1039983830389510305"))
Hope this helps.
async function mbr() {
const mB = await client.guilds.cache.get("1037783624449282189").members.fetch(`${args[0]}`).then((m) => { return m; });
return mB
}
let member = await mbr()
if (member.roles.cache.has("1039983830389510305"))

JS promises: is this promise equivalent to this async/await version?

If I have the following code
new Promise(res => res(1))
.then(val => console.log(val))
is this equivalent to
let val = await new Promise(res => res(1))
console.log(val)
I know one difference is that I have to wrap the second one in an async function, but otherwise are they equivalent?
Because your promise always resolves (never rejects), they are equivalent. You could also do:
Promise.resolve(1).then(val => console.log(val));
Keep in mind that a major difference with await (besides it needs to be wrapped in an async function) is what happens when the promise rejects. Though your example is hard-wired to resolve, not reject, lets look at what they look like with actual error handling (which you should always have):
new Promise(res => res(1))
.then(val => console.log(val))
.catch(err => console.log(err));
And:
try {
let val = await new Promise(res => res(1));
console.log(val);
} catch(e) {
console.log(err);
}
Or, if you didn't have the try/catch, then any rejects would automatically be sent to the promise that was automatically returned by the async function. This automatic propagation of errors (both synchronous exceptions and asynchronous rejections) is very useful in an async function.
It becomes more apparent when you have multiple asynchronous operations in series:
const fsp = require('fs').promises;
async function someFunc() {
let handle = await fsp.open("myFile.txt", "w");
try {
await handle.write(...);
await handle.write(...);
} finally {
await handle.close().catch(err => console.log(err));
}
}
someFunc().then(() => {
console.log("all done");
}).catch(err => {
console.log(err);
});
Here, the async wrapper catches errors form any of the three await statements and automatically returns them all to the caller. The finally statement catches either of the last two errors in order to close the file handle, but lets the error continue to propagate back to the caller.

async await utility function synchronization?

If we define a utility function like this:
/**
* Delete the entire database
*/
export async function deleteDatabase(db) {
await db.delete()
}
And we call it.
deleteDatabase(db)
nextFunction()
Then next function will not be called before the db.delete() has completed correct?
I think this is how it works, so I just want to confirm the semantics.
DexieJS Demo
Incorporating the answers into this DexieJS Demo for those interested in the actual use case:
https://stackblitz.com/edit/typescript-dexie-one-to-many
"Then next function will not be called before the db.delete() has completed correct?" Wrong. It will be called BEFORE async operation has completed.
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
async function deleteDatabase() {
console.log('Deleting DB')
await delay(1000)
console.log('DB deleted')
}
function nextFunction() {
console.log('Next function')
}
deleteDatabase()
nextFunction()
You need to have another async/await wrapper function or use .then to chain promises
deleteDatabase().then(nextFunction)
deleteDatabase(db)
This expression returns with promise immediately.
So nextFunction() will be called before the db.delete() has completed
You should await for deleteDatabase(db) as well
If they're in a async context, the next function won't be called, only if you put the await before the deleteDatabase
Like this:
async myFunc(db) {
await deleteDatabase(db)
nextFunction()
}
Or even:
async myFunc(db) {
return deleteDatabase(db)
.then(() =>
nextFunction()
)
}
If you call it like
deleteDatabase(db)
nextFunction()
The next function will be called immediately as you are not awaiting it.
You need to await
await deleteDatabase(db)
nextFunction()

return async function undefined in es6

Here, plcs variable within try catch function need to be used outside. I am trying calling async function directly and storing in sync function, both doesn't work, first method shows undefined and next one returns null promise
const fetch = () =>{
const result=async()=>{
try {
const dbResult = await ftchplc();
plcs= dbResult.rows._array;
return plcs
} catch (err) {
throw err;
}
}
return result()
}
const sample = fetch()
console.log(sample)
const result=async()=>{
try {
const dbResult = await ftchplc();
plcs= dbResult.rows._array;
return plcs
} catch (err) {
throw err;
}
}
result()
const sample = result()
ALL async functions return a promise. So you will have to use await or .then() when you call an async function in order to get the resolved value.
async functions are useful INSIDE the function so you can use await internal to the function, but to the outside caller, it's still just a promise being returned. async functions to not turn an asynchronous result into a synchronous result. The caller of an async function still has to deal with an asynchronous response (in a promise).
For example:
async function fn() {
const dbResult = await ftchplc();
return dbResult.rows._array;
};
fn().then(sample => {
console.log(sample);
}).catch(err => {
console.log(err);
});
This assumes that ftchplc() returns a promise that resolves to your expected dbResult.

Does a function need to be declared as asynchronous to return a promise?

I'm using a helper function (fetchGet) returning an asynchronous function that returns a promise (fetchWrapper).
Does this helper function need to be declared as asynchronous to itself return a promise?
My case here is using fetch, which needs to be awaited in my first function fetchWrapper (simplified here for readibility):
// returns a promise
async function fetchWrapper(url, method) {
const response = await fetch(url, {method: method});
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response;
}
async function fetchGet(url) {
return fetchWrapper(url, 'GET');
}
async function getSpecificData() {
return fetchGet('/a/specific/url');
}
Do I need to declare the fetchGet function as an asynchronous function as above, for it to return the promise?
Or could I just declare it as a normal synchronous function as below?
(that would be indeed the same case for the getSpecificData function)
function fetchGet(url) {
return fetchWrapper(url, 'GET');
}
Does a function need to be declared as asynchronous to return a promise?
No, not at all. In fact, promises were around long before async functions.
Your wrapper can be just:
function fetchGet(url) {
return fetchWrapper(url, 'GET');
}
You don't need async if you're not using await inside the function. You might choose to have it in order to flag up the asynchronous nature of the function, e.g., as in-code documentation (code hints in IDEs, etc.). But it isn't required.
Side note: You have a problem with fetchWrapper. It succeeds with the fulfillment value undefined if there's an HTTP error. That means code using it has to check the fulfillment value to see if it's undefined before using it. I'd recommend making HTTP errors errors (rejections):
async function fetchWrapper(url, method) {
const response = await fetch(url, {method: method});
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response;
}
Does this helper function need to be declared as asynchronous to itself return a promise?
No.
async functions always return promises, even if the return statement returns something that is not a promise. async functions let you manage other promises inside them using await
You can explicitly return a promise from any function.
You just need to declare a function as async when inside the function you going to await for a result, so both:
// returns a Promise because it's async (resolved with 'value')
async function fetchGet(url) {
const value = await fetchWrapper(url, 'GET');
return value;
}
// returns a Promise because fetchWrapper is a Promise
function fetchGet(url) {
return fetchWrapper(url, 'GET');
}
// returns a Promise, just because it's async
async function fetchGet(url) {
// nothing
}
// returns undefined
function fetchGet(url) {
// nothing
}
Work exactly the same for these callers:
fetchGet('url').then(value => {...})
const value = await fetchGet(url)

Categories

Resources