New to js,I want to output 5 '!'s with a 1-second interval,and finally output "end". I thought the problem was related to asynchronization. I have tried a lot of times and a lot of methods such as "await,async" and "promise" ,but still failed.
class A {
constructor() {
this.cnt = 5;
}
real() {
this.cnt--;
console.log("!");
}
getCnt() {
return this.cnt;
}
}
class B {
constructor() {
this.a = new A();
}
fun() {
if (this.a.getCnt() > 0) {
this.a.real();
setTimeout(() => this.fun(), 1000);
}
}
}
class C {
constructor() {
this.b = new B();
}
f() {
this.b.fun();
console.log("end");
}
}
var c = new C();
c.f();
Skipping the complexity of the 3 classes involved, this is elegantly solved with async functions. (Without async functions, the cascade of setTimeouts in a loop becomes more difficult to manage.)
This can of course be wrapped into a trio of classes if required.
// Turns `setTimeout` into a promise you can `await`.
async function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// Calls a function `fun` `times` times, delaying for `delayTime` ms between each invocation.
// Since this is an async function, it returns a promise that will resolve as soon as it's done,
// which in turn can be awaited upon.
async function repeatWithDelay(fun, times, delayTime) {
for (let i = 0; i < times; i++) {
await fun(i);
await delay(delayTime);
}
}
// Prints the number passed in (could just print an exclamation mark).
function print(i) {
console.log(`${i}!`);
}
async function main() {
console.log("Go!");
await repeatWithDelay(print, 5, 500);
console.log("Done!");
}
// Assumes top-level `await` is not available in your environment.
// If it is, this too can be replaced with a simple `await`.
main().then(() => {
console.log("Main done.");
});
This prints out
Go!
0!
1!
2!
3!
4!
Done!
Main done.
Ultimately you need to make your method fun be able to report when it is finished as it is an asynchronous method (calls setTimeout). The best way to do this is return a Promise which allows calls to use await on it
fun() {
return new Promise(resolve => {
const exec = () => {
if (this.a.getCnt() > 0) {
this.a.real();
setTimeout(exec.bind(this), 1000);
}
else{
resolve();
}
}
exec.apply(this);
})
}
Once you've done that, also mark function f as async which allows you to call await inside it:
async f() {
await this.b.fun();
console.log("end");
}
And then all works as expected:
class A {
constructor() {
this.cnt = 5;
}
real() {
this.cnt--;
console.log("!");
}
getCnt() {
return this.cnt;
}
}
class B {
constructor() {
this.a = new A();
}
fun() {
return new Promise(resolve => {
const exec = () => {
if (this.a.getCnt() > 0) {
this.a.real();
setTimeout(exec.bind(this), 1000);
}
else{
resolve();
}
}
exec.apply(this);
})
}
}
class C {
constructor() {
this.b = new B();
}
async f() {
await this.b.fun();
console.log("end");
}
}
var c = new C();
c.f();
I tried to write a generic function that emulate typing, hope this help you understand async/await functions
let fakeConsole = document.getElementById('console');
async function sleep(time) {
await new Promise(r => setTimeout(r, time));
}
function type(text) {
fakeConsole.innerHTML += text;
}
async function emulateTyping(text, speed) {
// split the text into an array of characters;
const characters = text.split('').reverse();
if (characters.length > 0) {
// display first character right away
type(characters.pop());
while (characters.length > 0) {
// wait <speed> millisections
await sleep(speed);
// type one character
type(characters.pop());
}
}
}
async function main() {
// async function we wait for the end with "await"
await emulateTyping("!!!!!", 500);
// sync function
type(' END');
};
main();
<pre id="console"></pre>