I've got a class that does something like the following:
class Loader
{
async load()
{
let pending = [...];
return new Promise( (resolve, reject) =>
{
while(pending.length > 0)
{
await this._loadAsset(pending.pop());
}
resolve();
});
}
async _loadAsset(asset)
{
return new Promise( (resolve, reject) =>
{
// Loading logic goes here, eventually calling...
resolve();
});
}
}
(pending is an array of objects that I'm too lazy to list out here.)
The problem I'm having is that Chrome is giving me an Uncaught SyntaxError: await is only valid in async function error when I try to load my script. As far as I can tell, Loader's load and _loadAsset methods have both been declared async. What am I missing? Is the while loop complicating things?
The callback function is not declared async, so you could try adding async like:
return new Promise( async (resolve, reject) =>
{
while(pending.length > 0) {
await this._loadAsset(pending.pop());
}
resolve();
});
But an even better option for resolving a lot of pending promised is the Promise.all() function
You can simplify your load function to:
async load()
{
let pending = [...];
while(pending.length > 0)
{
await this._loadAsset(pending.pop());
}
}
Theres no need for the inner promise construction.
You could also do this:
load()
{
let pending = [...];
return Promise.all(pending.map(i => this._loadAsset(i)));
}
The problem is that the anonymous function you're passing to the Promise constructor uses await but is not declared async:
return new Promise( (resolve, reject) => // this function uses await but is not async
{
while(pending.length > 0)
{
await this._loadAsset(pending.pop());
}
resolve();
});
Generally you won't need to use the Promise constructor if you're not wrapping something that takes a callback. In this case, you don't need it at all, you can just have the while loop be part of load's body:
async load()
{
let pending = [/*...*/];
while(pending.length > 0)
{
await this._loadAsset(pending.pop());
}
}
You should not return a promise from an async function. That's because under the hood, any value returned from async functions will be wrapped into a promise:
// The following function
async function foo() {
await someAsyncFunction();
return "done";
}
// Is the same as
function bar() {
return new Promise((resolve) => {
someAsyncFunction().then(() => resolve("done"));
});
}
So your code should be rewritten to:
class Loader
{
async load()
{
let pending = [...];
while(pending.length > 0)
{
await this._loadAsset(pending.pop());
}
}
async _loadAsset(asset)
{
// Loading logic happens here, but instead of calling resolve(), just return. Remember to add await to any async operation.
}
}
Related
Anticipated FAQ:
Yes, I know what a Promise is.
No, I can't simply move the init logic to the constructor. It needs to be called in the initMethod because the initMethod is a hook that needs to be called at a certain time.
Sorry, it's just that I saw some similar questions marked as "duplicate", so I wanted to put these FAQ at the top.
Question
My issue is the following race condition:
class Service {
private x: string | null = null;
initMethod() {
this.x = 'hello';
}
async methodA() {
return this.x.length;
}
}
const service = new Service();
setTimeout(() => service.initMethod(), 1000);
service.methodA().catch(console.log);
TypeError: Cannot read properties of null (reading 'length')
at Service.methodA (<anonymous>:15:19)
at <anonymous>:20:9
at dn (<anonymous>:16:5449)
I need something like a Promise whose settled value can be set from another part of the code. Something like:
class Service {
private x: SettablePromise<string> = new SettablePromise();
initMethod() {
this.x.set('hello');
}
async methodA() {
return (await this.x).length;
}
}
const service = new Service();
setTimeout(() => service.initMethod(), 1000);
service.methodA().catch(console.log);
The best I can come up with is to make a class that polls a value until it turns non-null. I'm hoping there's something smarter. I don't want to have to fine-tune a poll interval.
Edits
Sorry for the initial confusing example. The race condition is that methodA can be called before initMethod.
There was also an unnecessary async in the initMethod. I just made it async because the real method it was based on is async.
In the following example, you can run the init before or after the async method call. Either will work -
const s = new Service()
// init
s.init()
// then query
s.query("SELECT * FROM evil").then(console.log)
const s = new Service()
// query first
s.query("SELECT * FROM evil").then(console.log)
// then init
s.init()
deferred
The solution begins with a generic deferred value that allows us to externally resolve or reject a promise -
function deferred() {
let resolve, reject
const promise = new Promise((res,rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
service
Now we will write Service which has a resource deferred value. The init method will resolve the resource at some point in time. The asynchronous method query will await the resource before it proceeds -
class Service {
resource = deferred() // deferred resource
async init() {
this.resource.resolve(await connect()) // resolve resource
}
async query(input) {
const db = await this.resource.promise // await resource
return db.query(input)
}
}
connect
This is just some example operation that we run in the init method. It returns an object with a query method that mocks a database call -
async function connect() {
await sleep(2000) // fake delay
return {
query: (input) => {
console.log("running query:", input)
return ["hello", "world"] // mock data result
}
}
}
function sleep(ms) {
return new Promise(r => setTimeout(r, ms))
}
demo
function deferred() {
let resolve, reject
const promise = new Promise((res,rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
class Service {
resource = deferred()
async init() {
this.resource.resolve(await connect())
}
async query(input) {
const db = await this.resource.promise
return db.query(input)
}
}
async function connect() {
await sleep(2000)
return {
query: (input) => {
console.log("running query:", input)
return ["hello", "world"]
}
}
}
function sleep(ms) {
return new Promise(r => setTimeout(r, ms))
}
const s = new Service()
s.query("SELECT * FROM evil").then(console.log)
s.init()
always handle rejections
If init fails to connect to the database, we need to reflect that with the resource, otherwise our program will hang indefinitely -
class Service {
resource = deferred()
async init() {
try {
const db = await timeout(connect(), 5000) // timeout
this.resource.resolve(db)
}
catch (err) {
this.resource.reject(err) // reject error
}
}
async query(input) {
const db = await timeout(this.resource.promise, 5000) // timeout
return db.query(input)
}
}
function timeout(p, ms) {
return Promise.race([
p,
sleep(ms).then(() => { throw Error("timeout") }),
])
}
As I understand it, you need to have a promise settled within constructor but it have to be resolved only when initMethod is called.
You can expose a promise resolver alongside the promise :
class Service {
private x: string | null = null;
private promise;
private resolve;
constructor() {
this.promise = new Promise(resolve => this.resolve = resolve);
}
async initMethod() {
// Do your async stuff
await new Promise(resolve => setTimeout(resolve, 1000));
this.x = 'hello';
// And resolve Service promise
this.resolve();
}
async methodA() {
await this.promise;
return this.x.length;
}
}
See Resolve Javascript Promise outside the Promise constructor scope for more examples.
You need to initialise a property with a Promise value that only resolves after initMethod() has completed.
This involves also maintaining the promise's resolve callback as a class property.
class Service {
#initResolve;
#initPromise = new Promise((resolve) => {
this.#initResolve = resolve;
});
#x = null;
async initMethod() {
await new Promise(resolve => setTimeout(resolve, 1000));
this.#x = 'hello';
this.#initResolve(); // resolve #initPromise
}
async methodA() {
await this.#initPromise;
return this.#x.length;
}
}
const service = new Service();
console.log("methodA: start");
service.methodA().then(console.log.bind(console, "methodA: end:"));
setTimeout(async () => {
console.log("initMethod: start");
await service.initMethod()
console.log("initMethod: end");
}, 1000);
Note: Using JavaScript private class features for the Snippet but I'm sure you can translate it to Typescript.
How can i return this function a promise
i want to return this function a promise function but don't know how to return a promise
return new Promise((resolve, reject) => {});
async function readFile(filePath) {
const myInterface = readline.createInterface({
input: fs.createReadStream(filePath),
});
const arrayLink = [],
arrayName = [];
for await (const line of myInterface) {
let temp = line.split(",");
arrayLink.push(temp[0]);
arrayName.push(temp[1]);
}
return [arrayLink, arrayName];
}
You can return a promise from the function. But in that case, to make that function async does not make any sense though it will work fine.
When a function returns a promise you don't need to write then block. You can handle it with await keyword. Whatever you provided in resolve() function, you will get that value in variable - result
If you want to return a promise from your function following is the way.
function readFile() {
return new Promise((resolve, reject)=>{
// your logic
if(Success condition met) {
resolve(object you want to return);
}else{
reject(error);
// you can add error message in this error as well
}
});
}
async function fileOperation() {
let result = await readFile();
}
I'm using the async.eachLimit function to control the maximum number of operations at a time.
const { eachLimit } = require("async");
function myFunction() {
return new Promise(async (resolve, reject) => {
eachLimit((await getAsyncArray), 500, (item, callback) => {
// do other things that use native promises.
}, (error) => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
As you can see, I can't declare the myFunction function as async because I don't have access to the value inside the second callback of the eachLimit function.
You're effectively using promises inside the promise constructor executor function, so this the Promise constructor anti-pattern.
Your code is a good example of the main risk: not propagating all errors safely. Read why there.
In addition, the use of async/await can make the same traps even more surprising. Compare:
let p = new Promise(resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Catches it.
with a naive (wrong) async equivalent:
let p = new Promise(async resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Doesn't catch it!
Look in your browser's web console for the last one.
The first one works because any immediate exception in a Promise constructor executor function conveniently rejects the newly constructed promise (but inside any .then you're on your own).
The second one doesn't work because any immediate exception in an async function rejects the implicit promise returned by the async function itself.
Since the return value of a promise constructor executor function is unused, that's bad news!
Your code
There's no reason you can't define myFunction as async:
async function myFunction() {
let array = await getAsyncArray();
return new Promise((resolve, reject) => {
eachLimit(array, 500, (item, callback) => {
// do other things that use native promises.
}, error => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
Though why use outdated concurrency control libraries when you have await?
I agree with the answers given above and still, sometimes it's neater to have async inside your promise, especially if you want to chain several operations returning promises and avoid the then().then() hell. I would consider using something like this in that situation:
const operation1 = Promise.resolve(5)
const operation2 = Promise.resolve(15)
const publishResult = () => Promise.reject(`Can't publish`)
let p = new Promise((resolve, reject) => {
(async () => {
try {
const op1 = await operation1;
const op2 = await operation2;
if (op2 == null) {
throw new Error('Validation error');
}
const res = op1 + op2;
const result = await publishResult(res);
resolve(result)
} catch (err) {
reject(err)
}
})()
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e));
The function passed to Promise constructor is not async, so linters don't show errors.
All of the async functions can be called in sequential order using await.
Custom errors can be added to validate the results of async operations
The error is caught nicely eventually.
A drawback though is that you have to remember putting try/catch and attaching it to reject.
BELIEVING IN ANTI-PATTERNS IS AN ANTI-PATTERN
Throws within an async promise callback can easily be caught.
(async () => {
try {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}
catch (BALL) {
console.log ("(A) BALL CAUGHT", BALL);
throw BALL;
}
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
or even more simply,
(async () => {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
I didn't realized it directly by reading the other answers, but what is important is to evaluate your async function to turn it into a Promise.
So if you define your async function using something like:
let f = async () => {
// ... You can use await, try/catch, throw syntax here (see answer of Vladyslav Zavalykhatko) ..
};
your turn it into a promise using:
let myPromise = f()
You can then manipulate is as a Promise, using for instance Promise.all([myPromise])...
Of course, you can turn it into a one liner using:
(async () => { code with await })()
static getPosts(){
return new Promise( (resolve, reject) =>{
try {
const res = axios.get(url);
const data = res.data;
resolve(
data.map(post => ({
...post,
createdAt: new Date(post.createdAt)
}))
)
} catch (err) {
reject(err);
}
})
}
remove await and async will solve this issue. because you have applied Promise object, that's enough.
I have a simple yet perplexing issue with async functions.
I wish to simply return the value when its ready from the function.
Here is a sample code:
async function test() {
setTimeout(function() {
return 'eeeee';
}, 5000);
}
test().then(x => {
console.log(x)
});
You will get undefined been logged at once.
It's clear that you are trying to write a sleep() async function, but do remember that setTimeout is a sync function calling with a callback function will be executed at a given time, so while you are executing test(), the calling will run to end and return undefined as you have no return statement in the function body, which will be passed to your .then() function.
The right way to do this is to return a Promise that will be resolved after a given time, that will continue the then call.
async function sleep(time){
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve("echo str")
},time)
})
}
sleep(5000).then((echo) => console.log(echo))
sleep function in short
const sleep = async time => new Promise(resolve=>setTimout(resolve,time))
With Promises
const setTimer = (duration) => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Done!');
}, duration);
});
return promise;
};
setTimer(2000).then((res) => console.log(res));
An async function has to return a promise. So to fix this, you can wrap your setTimeout function in a new promise like so:
async function test(){
return await new Promise((resolve, reject) => {
setTimeout(function(){
resolve('eeeee');
},5000);
})
}
test().then(x => {
console.log(x)
});
You can learn more about async/await on the MDN docs here. Hope this helps!
I'm using the async.eachLimit function to control the maximum number of operations at a time.
const { eachLimit } = require("async");
function myFunction() {
return new Promise(async (resolve, reject) => {
eachLimit((await getAsyncArray), 500, (item, callback) => {
// do other things that use native promises.
}, (error) => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
As you can see, I can't declare the myFunction function as async because I don't have access to the value inside the second callback of the eachLimit function.
You're effectively using promises inside the promise constructor executor function, so this the Promise constructor anti-pattern.
Your code is a good example of the main risk: not propagating all errors safely. Read why there.
In addition, the use of async/await can make the same traps even more surprising. Compare:
let p = new Promise(resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Catches it.
with a naive (wrong) async equivalent:
let p = new Promise(async resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Doesn't catch it!
Look in your browser's web console for the last one.
The first one works because any immediate exception in a Promise constructor executor function conveniently rejects the newly constructed promise (but inside any .then you're on your own).
The second one doesn't work because any immediate exception in an async function rejects the implicit promise returned by the async function itself.
Since the return value of a promise constructor executor function is unused, that's bad news!
Your code
There's no reason you can't define myFunction as async:
async function myFunction() {
let array = await getAsyncArray();
return new Promise((resolve, reject) => {
eachLimit(array, 500, (item, callback) => {
// do other things that use native promises.
}, error => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
Though why use outdated concurrency control libraries when you have await?
I agree with the answers given above and still, sometimes it's neater to have async inside your promise, especially if you want to chain several operations returning promises and avoid the then().then() hell. I would consider using something like this in that situation:
const operation1 = Promise.resolve(5)
const operation2 = Promise.resolve(15)
const publishResult = () => Promise.reject(`Can't publish`)
let p = new Promise((resolve, reject) => {
(async () => {
try {
const op1 = await operation1;
const op2 = await operation2;
if (op2 == null) {
throw new Error('Validation error');
}
const res = op1 + op2;
const result = await publishResult(res);
resolve(result)
} catch (err) {
reject(err)
}
})()
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e));
The function passed to Promise constructor is not async, so linters don't show errors.
All of the async functions can be called in sequential order using await.
Custom errors can be added to validate the results of async operations
The error is caught nicely eventually.
A drawback though is that you have to remember putting try/catch and attaching it to reject.
BELIEVING IN ANTI-PATTERNS IS AN ANTI-PATTERN
Throws within an async promise callback can easily be caught.
(async () => {
try {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}
catch (BALL) {
console.log ("(A) BALL CAUGHT", BALL);
throw BALL;
}
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
or even more simply,
(async () => {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
I didn't realized it directly by reading the other answers, but what is important is to evaluate your async function to turn it into a Promise.
So if you define your async function using something like:
let f = async () => {
// ... You can use await, try/catch, throw syntax here (see answer of Vladyslav Zavalykhatko) ..
};
your turn it into a promise using:
let myPromise = f()
You can then manipulate is as a Promise, using for instance Promise.all([myPromise])...
Of course, you can turn it into a one liner using:
(async () => { code with await })()
static getPosts(){
return new Promise( (resolve, reject) =>{
try {
const res = axios.get(url);
const data = res.data;
resolve(
data.map(post => ({
...post,
createdAt: new Date(post.createdAt)
}))
)
} catch (err) {
reject(err);
}
})
}
remove await and async will solve this issue. because you have applied Promise object, that's enough.