If I have these lines:
const service = await getService('player');
const players = await service.players();
Can I use this one-liner version instead? Are they equivalent?
const players = await (await getService('player')).players();
Is there an even more streamlined way to write it?
Short answer: It's equivalent to in terms of result.
However, As #CertainPerformance's comment, you should use the first snippet to be able to debugger (as well as to follow best practice) like this.
let getService = (str) => new Promise(resolve => setTimeout(() => resolve({players: getPlayers}), 1000));
let getPlayers = () => new Promise(resolve => setTimeout(() => resolve("Response data"), 1000));
async function run() {
const service = await getService('player');
console.log(service); // debugger to watch response of each async func
const players = await service.players();
console.log(players); // debugger to watch response of each async func
}
run();
Related
Consider this really simple example:
class MyClass {
public add(num: number): number {
return num + 2;
}
}
const result = await page.evaluate((NewInstance) => {
console.log("typeof instance", typeof NewInstance); // undefined
const d = new NewInstance();
console.log("result", d.add(10));
return d.add(10);
}, MyClass);
I've tried everything I could think of. The main reason I want to use a class here, is because there's a LOT of code I don't want to just include inside the evaluate method directly. It's messy and hard to keep track of it, so I wanted to move all logic to a class so it's easier to understand what's going on.
Is this possible?
It's possible, but not necessarily great design, depending on what you're trying to do. It's hard to suggest the best solution without knowing the actual use case, so I'll just provide options and let you make the decision.
One approach is to stringify the class (either by hand or with .toString()) or put it in a separate file, then addScriptTag:
const puppeteer = require("puppeteer"); // ^19.6.3
class MyClass {
add(num) {
return num + 2;
}
}
let browser;
(async () => {
browser = await puppeteer.launch();
const [page] = await browser.pages();
await page.goto(
"https://www.example.com",
{waitUntil: "domcontentloaded"}
);
await page.addScriptTag({content: MyClass.toString()});
const result = await page.evaluate(() => new MyClass().add(10));
console.log(result); // => 12
})()
.catch(err => console.error(err))
.finally(() => browser?.close());
See this answer for more examples.
Something like eval is also feasible. If it looks scary, consider that anything you put into a page.evaluate() or page.addScriptTag() is effectively the same thing as far as security goes.
const result = await page.evaluate(MyClassStringified => {
const MyClass = eval(`(${MyClassStringified})`);
return new MyClass().add(10);
}, MyClass.toString());
Many other patterns are also possible, like exposing your library via exposeFunction if the logic is Node-based rather than browser-based.
That said, defining the class inside an evaluate may not be as bad as you think:
const addTonsOfCode = () => {
MyClass = class {
add(num) {
return num + 2;
}
}
// ... tons of code ...
};
let browser;
(async () => {
browser = await puppeteer.launch();
const [page] = await browser.pages();
await page.goto(
"https://www.example.com",
{waitUntil: "domcontentloaded"}
);
await page.evaluate(addTonsOfCode);
const result = await page.evaluate(() => new MyClass().add(10));
console.log(result); // => 12
})()
.catch(err => console.error(err))
.finally(() => browser?.close());
I'd prefer to namespace this all into a library:
const addTonsOfCode = () => {
class MyClass {
add(num) {
return num + 2;
}
}
// ... tons of code ...
window.MyLib = {
MyClass,
// ...
};
};
Then use with:
await page.evaluate(addTonsOfCode);
await page.evaluate(() => new MyLib.MyClass().add(10));
(This seems partially overlapping with my previous question: Test interaction with two interval async functions)
Using jest 27.5.1 and sinon 13.0.1, I want to unit-test an async interval function.
Consider this code:
const fs = require("fs")
const { exec } = require("child_process")
module.exports = class MyClass {
init() {
setInterval(this.checkFileContent, 10)
}
async checkFileContent() {
await new Promise((resolve) => { exec("sleep 0.4", resolve) })
const fileContent = (await fs.promises.readFile("foo.txt")).toString()
console.log(fileContent)
}
}
and this test file
const MyClass = require(".")
const fs = require("fs")
const sinon = require("sinon")
const { exec } = require("child_process")
describe("MyClass", () => {
let sandbox
beforeAll(async () => {
sandbox = sinon.createSandbox()
await fs.writeFileSync("foo.txt", "fooContent")
})
afterAll(async () => {
await fs.unlinkSync("foo.txt")
await new Promise((resolve) => { exec("sleep 0.8", resolve) })
sandbox.verifyAndRestore()
})
test("should test MyClass", async () => {
const clock = sandbox.useFakeTimers()
const instance = new MyClass()
instance.init()
clock.next()
await Promise.resolve() // As suggested in this answer https://stackoverflow.com/a/52196951/2710714 - but doesn't seem to help at all
clock.reset()
})
})
Running the test, I'll always get ENOENT: no such file or directory, open 'foo.txt'. The reason being that await fs.unlinkSync("foo.txt") has already run when await fs.promises.readFile("foo.txt") is reached.
So, I need to somehow make sure that all promises in the interval function started by clock.next() are resolved before the tests stops. How do I do that?
Note: In real-life, it's not about file access but database queries. And the exec("sleep ..." is of course something artificial to replicate long-lasting operations. But the principles should be the same.
Edit: Also tried await clock.nextAsync() - didn't help.
I'm just trying to understand the benefits of this:
const populateUsers = done => {
User.remove({}).then(async () => {
const userOne = new User(users[0]).save();
const userTwo = new User(users[1]).save();
const usersProm = await Promise.all([userOne, userTwo]).then(() => done());
return usersProm;
});
};
over this:
const populateUsers = done => {
User.remove({})
.then(() => {
const userOne = new User(users[0]).save();
const userTwo = new User(users[1]).save();
return Promise.all([userOne, userTwo]);
})
.then(() => done());
};
I came to this problem because eslint suggested my to use async in this function, and I remember the concept, make it work in my app, but I'm not sure why should I use this instead of the original way
Your original code was totally fine.
No, there is no benefit in using the code from your first snippet. You should avoid mixing await and .then(…) syntax! To use async/await, you'd make the whole function async, not the then callback:
async function populateUsers(done) {
await User.remove({})
const userOne = new User(users[0]).save();
const userTwo = new User(users[1]).save();
await Promise.all([userOne, userTwo]);
return done();
}
(Probably you would also remove that done callback - the function already returns a promise)
Your first version does not go all the way. Do this:
const populateUsers = done => {
User.remove({}).then(async () => {
const userOne = new User(users[0]).save();
const userTwo = new User(users[1]).save();
await Promise.all([userOne, userTwo]);
const usersProm = await done();
return usersProm;
});
};
There is no difference, it is just that code without these then callbacks is somewhat easier to read.
You might even apply it to the outer function:
const populateUsers = async () => {
await User.remove({});
const userOne = new User(users[0]).save();
const userTwo = new User(users[1]).save();
await Promise.all([userOne, userTwo]);
const usersProm = await done();
return usersProm;
};
Now populateUsers returns the promise instead of undefined.
As concluded in comments: you get an error because populateUsersreturns a promise and accepts a done callback argument, while one of these is expected, not both.
const getInfoFromURL = async() => {
const a = await test1();
const b = await test2();
};
const test1 = async() => {
setTimeout(() => {
console.log('test 1');
},6000);
};
const test2 = async() => {
setTimeout(() => {
console.log('test 2');
},6000);
};
getInfoFromURL();
Example case: after 6 sec shown message /test1/ , and then after another 6 sec shown message /test2/.
Result: after 6 sec immediately show 2 message test1 and test2.
Can i realize this case withot using promise?
I'm assuming what you actually want to achieve is running const a = await test1(); and const b = await test2(); in parallell. With that in mind I've taken the liberty to change the surrounding mockup code a bit to show you how to do it in parallell:
const getInfoFromURL = async() => {
const asyncResult1 = test1(), asyncResult2 = test2();
return [await asyncResult1, await asyncResult2];
};
const test1 = async() => {
return await new Promise(resolve => setTimeout(resolve, 6000)).then(() => 'test1');
};
const test2 = async() => {
return await new Promise(resolve => setTimeout(resolve, 6000)).then(() => 'test2');
};
console.time('timer');
getInfoFromURL().then((result) => { console.timeEnd("timer"); console.log(result)});
This should output timer: 600*.***ms and ['test1', 'test2'].
I've changed the test functions to use promises and .then return "test1" and "test2", and for the actual call of getInfoFromUrl() to use .then to allow us to console log when it resolves (so we can actually measure the time it takes) as I consider these changes not relevant to the problem you're trying to solve.
Anyway the important part is the content in the getInfoFromURL function. Note that writing return [await test1(), await test2()] will NOT work and they will be run sequentially.
The key is assigning the result of the functions to variables first (without awaiting the result), and then awaiting the same variables at a later time. In this case, the async operations have both already started before we're awaiting them.
I have a bunch of async functions, that I always or nearly always want to call synchronously. So we all know the pattern
async function somethingcool() {
return new Promise(resolve => {
setTimeout(resolve, 1000, "Cool Thing");
});
}
const coolthing = await somethingcool();
console.log(coolthing);
But I have this cool module called manycooolthings which offers many cool things, all via async functions that I always or nearly always want to await on.
import * as cool from 'manycoolthings';
await cool.updateCoolThings();
const coolThing = await cool.aCoolThing();
const anohtherCoolThing = await cool.anotherCoolThing();
const rus = await cool.coolThingsAreUs();
await cool.sendCoolThings();
await cool.postCoolThing(myCoolThing);
await cool.moreCoolThings();
const thingsThatAreCool = await cool.getThingsThatAreCool();
Extremely contrived and silly example, to illustrate the point. I do have a genuine use case, a set of tests based on puppeteer where most functions are async and they almost always want to be awaited on.
There must be a better way to avoid all the await pollution of our JavaScript code.
It would be great if could do something like
import * as cool from 'manycoolthings';
await {
cool.updateCoolThings();
const coolThing = cool.aCoolThing();
const anotherCoolThing = cool.anotherCoolThing();
const rus = cool.coolThingsAreUs();
cool.sendCoolThings();
cool.postCoolThing(myCoolThing);
cool.moreCoolThings();
const thingsThatAreCool = cool.getThingsThatAreCool();
}
Or even just
import * as cool from 'manycoolthings';
cool.updateCoolThings();
const coolThing = cool.aCoolThing();
const anotherCoolThing = cool.anotherCoolThing();
const rus = cool.coolThingsAreUs();
cool.sendCoolThings();
cool.postCoolThing(myCoolThing);
cool.moreCoolThings();
const thingsThatAreCool = cool.getThingsThatAreCool();
without having to worry if the method being called is async or not, because it's defined as an auto await function or something.
If you're unhappy with multiple awaits or thens, you can make a little "sequence" helper:
let _seq = async fns => fns.reduce((p, f) => p.then(f), Promise.resolve(null))
and use it like this:
result = await seq(
_ => cool.updateCoolThings(),
_ => _.aCoolThing(),
_ => _.anotherCoolThing(),
_ => _.coolThingsAreUs(),
)
which is almost your snippet #2.