尝试在承诺中设置递归函数

Trying to set a recursive function inside a promise

let cancelDownload = true
const delay = () => {
    return new Promise(() => {
            if (cancelDownload === true){
                setTimeout(() => {
                    console.log('Delaying...');
                    delay();
                }, 1000);
            }
            else return null;
            });
};
const cancelJob = async() => {
    for(let i = 6; i>0; i--){
        console.log('inside for ',i);
        setTimeout(()=>{
            cancelDownload = false
        },4000)
        await delay()
        console.log('aaaaaaaa');
        console.log(`the number is ${i}`);
    }
}

cancelJob()

我正在尝试编写一个延迟函数,一旦满足条件,延迟就会被删除并恢复所有代码,但似乎一旦延迟完成,代码就会退出而不执行最后两个控制台日志

你传递给 promise 构造函数(promise 执行器 函数)的函数被调用时带有两个参数:一个用于解析 promise 的函数和一个用于拒绝它的函数(通常称为 resolvereject)。当您的异步工作完成时,您的代码应该调用这些函数之一。

你的不是,所以承诺永远不会兑现,你的代码永远等待 await

但还有其他问题:

1。如果您再次调用 delay,它会创建一个 new 承诺。您使用 await 的代码只能访问第一个承诺,而不能访问那些递归调用创建的承诺。根本没有任何理由在这里使用递归。

2。对该函数的所有调用共享 相同的 标志。因此,如果我们通过不履行承诺来解决问题,循环会等待,但只会等待一次:

let cancelDownload = false;
const delay = () => {
    return new Promise((resolve) => {
        tick();
        function tick() {
            // Cancelled?
            if (cancelDownload) {
                // Yes, fulfill the promise
                console.log("Flag is set, fulfilling");
                resolve(null);
            } else {
                // No, wait another second
                console.log("waiting 1000 and checking again");
                setTimeout(tick, 1000);
            }
        }
    });
};
const cancelJob = async () => {
    setTimeout(() => {
        console.log("Setting cancelDownload to true");
        cancelDownload = true;
    }, 4000);
    for (let i = 6; i > 0; i--) {
        console.log("inside for ", i);
        await delay();
        console.log("aaaaaaaa");
        console.log(`the number is ${i}`);
    }
};

cancelJob();
.as-console-wrapper {
    max-height: 100% !important;
}

不过,我可能会误认为第二个是您的用例的问题,因为在对现已删除的答案的评论中,您说您希望循环只等待一次(“整个循环”而不是不仅仅是一次迭代)。

如果你想要一个轮询标志的函数(我不推荐它,轮询通常不是最佳实践,虽然有时你无法避免它)并在设置时履行承诺,你可以使用 AbortController:

const delay = (signal) => {
    let done = false;
    return new Promise((resolve) => {
        tick();
        function tick() {
            // Cancelled?
            if (signal.aborted) {
                // Yes, fulfill the promise with null
                console.log("Fulfilling with null");
                resolve(null);
            } else {
                // No, wait another second
                console.log("Waiting 1000 and checking again");
                setTimeout(tick, 1000);
            }
        }
    });
};
const cancelJob = async () => {
    const controller = new AbortController();
    setTimeout(() => {
        console.log("Cancelling");
        controller.abort();
    }, 4000);
    for (let i = 6; i > 0; i--) {
        console.log("inside for ", i);
        await delay(controller.signal);
        console.log("aaaaaaaa");
        console.log(`the number is ${i}`);
    }
};

cancelJob();
.as-console-wrapper {
    max-height: 100% !important;
}

这也只会延迟循环一次,因为我们将相同的信号传递给所有 delay 函数。最初我是在循环中创建的,如下所示:

const delay = (signal) => {
    let done = false;
    return new Promise((resolve) => {
        tick();
        function tick() {
            // Cancelled?
            if (signal.aborted) {
                // Yes, fulfill the promise with null
                console.log("Fulfilling with null");
                resolve(null);
            } else {
                // No, wait another second
                console.log("Waiting 1000 and checking again");
                setTimeout(tick, 1000);
            }
        }
    });
};
const cancelJob = async () => {
    for (let i = 6; i > 0; i--) {
        const controller = new AbortController();
        setTimeout(() => {
            console.log("Cancelling");
            controller.abort();
        }, 4000);
        console.log("inside for ", i);
        await delay(controller.signal);
        console.log("aaaaaaaa");
        console.log(`the number is ${i}`);
    }
};

cancelJob();
.as-console-wrapper {
    max-height: 100% !important;
}

...但后来我看到了另一个答案的“整个循环”评论。

注意:通常当您有一个带有取消功能的异步进程时,它会以特定于取消的拒绝原因拒绝取消,而不是完成,但您明白了。

不需要递归。相反,您可以:

  1. 使用setInteval每秒检查你的状态。
  2. 当条件正确时,您需要解决承诺。
  3. 使用clearInterval.

let cancelDownload = true
const delay = () => {
    let intervalId;
    return new Promise((resolve, reject) => {
      const check = () => {
        if (cancelDownload === true){
            console.log('Delaying...');
        } else  {
          clearInterval(intervalId);
          resolve();
        }
      }
      //check immediately
      check();
      //then check every second afterwards
      intervalId = setInterval(check, 1000);
    });
};
const cancelJob = async() => {
    for(let i = 6; i>0; i--){
        console.log('inside for ',i);
        setTimeout(()=>{
            cancelDownload = false
        },4000)
        await delay()
        console.log('aaaaaaaa');
        console.log(`the number is ${i}`);
    }
}

cancelJob()

这可以按以下方式概括一点 - 不是对条件进行硬编码,而是将其作为回调提供。然后你可以有一个 delay 函数而不使用全局变量,它可以等待不同的事情,而不仅仅是一个变量。

const delayWhile = shouldWait => () => {
    let intervalId;
    return new Promise((resolve, reject) => {
      const check = () => {
        if (shouldWait()){
            console.log('Delaying...');
        } else  {
          clearInterval(intervalId);
          resolve();
        }
      }
      //check immediately
      check();
      //then check every second afterwards
      intervalId = setInterval(check, 1000);
    });
};

const cancelJob = async() => {
    let cancelDownload = true;
    const delay = delayWhile(() => cancelDownload);
    
    for(let i = 6; i>0; i--){
        console.log('inside for ',i);
        setTimeout(()=>{
            cancelDownload = false
        },4000)
        await delay()
        console.log('aaaaaaaa');
        console.log(`the number is ${i}`);
    }
}

cancelJob()

可取消承诺的一种方法是为承诺创建者函数提供一个可写参数,如 cancelToken。 promise 创建者使用回调填充此参数,调用时会取消此特定 promise。这通常会导致更简单和更线性的代码。

let pause = () => new Promise(r => setTimeout(r, 100));

async function job(name, steps, cancelToken) {
    let cancelled = false;

    cancelToken.callback = () => cancelled = true;

    for (let step = 1; step <= steps; step++) {
        console.log(name, step);

        await pause();

        if (cancelled) {
            console.log('CANCELLED');
            break;
        }
    }
}


async function main() {
    let cancelToken = {};

    let job1 = job('ok', 5, cancelToken);
    await job1;

    let job2 = job('bad', 5, cancelToken);
    setTimeout(cancelToken.callback, 100);
    await job2;
}

main()

作为一般提示,最好尽可能避免 new Promise 和回调。请注意,在此示例中,PromisesetTimeout 仅用于测试目的(模拟实际工作),一旦我们将 pause 替换为更有用的内容,其余代码将不会更改,例如fetch.