简体   繁体   中英

While loop with Promise is stuck in infinite loop

I am running a while loop which contains a Promise with both success and failure callbacks. The Promise checks if an image successfully loads at an URL.

Looking at the code below, I continuously loop over a list of named photos 1.jpg , 2.jpg , and so on for example.com , sending a Promise each time.

However the loop is stuck in an infinite loop. My browser becomes unresponsive when I run the code below.

Why is this?

Also why do I get an illegal break statement when I use break; in the Promise failure callback?

function photoCheck(url) {
    return new Promise((resolve, reject) => {
        let img = document.createElement('img')
        img.onload = resolve
        img.onerror = reject
        img.src = url
    })
}

photoCount = 0;
while (running == true) {
    photoCount += 1;
    photo = 'example.com/' + photoCount + '.jpg';
    photoCheck(photo).then(() => {
        var img = document.createElement('img');
        img.src = photo;
        body.appendChild(img);
    }, () => {
        running = false
        // If I use `break;`, I get an illegal break statement error.
    }) 
}

It seems that you're not understanding how asynchronous JavaScript code and the JavaScript event loop work. I recommend reading the first three chapters of this book to get a better understanding of these concepts.

There is no possible way that what you have there could work because it is impossible for running to change to false before that loop finishes (and it never does). The then callbacks cannot execute as long as any blocking code is running.

One possible alternative is to use recursive logic to fetch the next photo after each one is retrieved. A drawback to this is that it will only fetch one photo at a time, but the benefit is that you will have at most one failed request:

function photoCheck(url) {
    return new Promise((resolve, reject) => {
        let img = document.createElement('img');
        img.onload = () => resolve(img);
        img.onerror = reject;
        img.src = url;
    })
}

function getPhoto(num) {
    const photo = 'example.com/' + num + '.jpg';

    return photoCheck(photo)
         .then((img) => {
             body.appendChild(img);

             return getPhoto(num + 1);
         });
}

getPhoto(1);

OTOH, since it looks like you're using a modern version of JavaScript, you could possibly use a loop in an async function:

function photoCheck(url) {
    return new Promise((resolve, reject) => {
        let img = document.createElement('img');
        img.onload = () => resolve(img);
        img.onerror = reject;
        img.src = url;
    })
}

async function getPhotos() {
    try {
        for (let photoNum = 1; ; photoNum += 1) {
            const photo = 'example.com/' + photoNum + '.jpg';

            body.appendChild(await photoCheck(photo));
        }
    } catch(e) { }
}

getPhotos().then(() => console.log('all done'));

Also why do I get an illegal break statement when I use break; in the Promise failure callback?

Because a break; statement has to be one of the statements in the loop. You're trying to put break; in a separate function , where it wouldn't be part of any loop.

Try this:

function photoCheck(url) {
    return new Promise((resolve, reject) => {
        let img = document.createElement('img')
        img.onload = resolve
        img.onerror = reject
        img.src = url
    })
}

function loadPhotos(current) {
    var photo = 'example.com/' + current + '.jpg';
    photoCheck(photo).then(() => {
        var img = document.createElement('img');
        img.src = photo;
        body.appendChild(img);
        loadPhotos(current + 1);
    }, () => {}) 
}

loadPhotos(1);

The problem with your code is that the promises' resolve callbacks never have a chance to execute, because your while loop is still running and there can't be two things running at the same time in JavaScript (unless if you use workers).

This is a very important concept when dealing with async code.

Another option is to use async/await:

function photoCheck(url) {
    return new Promise((resolve, reject) => {
        let img = document.createElement('img');
        img.onload = () => resolve(img);
        img.onerror = reject;
        img.src = url;
    })
}

(async function () {
    var photoCount = 0;
    while (true) {
        photoCount++;
        var photo = 'example.com/' + photoCount + '.jpg';

        try {
            var img = await photoCheck(photo);
            document.body.appendChild(img);
        } catch(e) {
            break;
        }
    }   
})();

But I recommend you to try to understand more deeply how async calls work before using async/await.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM