【问题标题】:Deep Promise.all深承诺.all
【发布时间】:2020-06-26 18:13:01
【问题描述】:

我有一个以保存文件列表结尾的承诺链。我想等到所有文件都保存或失败,然后继续我的链。问题是,当我在具有自己的子链的 Promise 列表中使用 Promise.all 时,Promise.all 上的 thens 在 Promise.all 内的 Promise 上的 thens 之前开始解决.请参阅下面的注释示例。

const fs = require('fs');
const util = require('util');
// Make a promise out of the file write function
const promiseWriteFile = (file, data, options) => {
    return util.promisify(fs.writeFile).call(fs, file, data, options);
};

...

console.log('Received files');
console.group();
// Start long promise chain
somePromise(...)
    .then((result) => {
        console.log('validating blah blah');
    })
    .then((result) => {
        ...
    })
    .then((result) => {
        // Now I need to save the files to the disk
        let path = './uploaded_files/';
        // Here I want to resolve all the file save promises, or find one that fails
        return Promise.all(
            request.files.map((file) => {
                let filename = path + Date.now() + '_' + file.originalname;
                // Purposely change `path` so that the next file write will fail
                path = './garbage/';
                console.log('Trying to save file:', filename);
                return promiseWriteFile(filename, file.buffer, 'binary')
                    .then(() => {
                        console.log('Saving file:', filename);
                    })
                    .catch((error) => {
                        console.log('Could not save file:', filename);
                        throw error;
                    });
            }),
        );
    })
    .then(() => { // <======= I don't want this to happen until the promises in the `Promise.all` above have fully, DEEPLY resolved
        // set success message
        message = 'Part successfully created.';
        console.groupEnd();
        console.log('Part created successfully.');
    })
    .catch((exception) => {
        message = exception.message;
        console.groupEnd();
        console.log('Part invalid.');
    });

输出如下:

Received files
  validating blah blah
  Trying to save file: ./uploaded_files/A.txt
  Trying to save file: ./garbage/B.txt
Part invalid.
Could not save file: ./garbage/B.txt
Saving file: ./uploaded_files/A.txt

如您所见,打印“无法保存文件”和“保存文件”的行在大承诺链的 then/catch 之后执行。你可以知道,因为console.groupEnd()首先被调用,然后有进一步的输出。

如何确保在所有子 Promise 都完成之前,大 Promise 链上的 then/catch 不会发生?

【问题讨论】:

    标签: javascript promise


    【解决方案1】:

    您在 map 的 catch 块中重新抛出错误

    .catch((error) => {
      console.log('Could not save file:', filename);
      throw error;
    });
    

    这会破坏您的Promise.all(),并导致它在完成所有承诺之前陷入困境。相反,您应该只是转发该错误而不是使错误链崩溃。您需要重新考虑如何确认所有写入以及以下步骤。

    稍后处理退货的示例:

        .then((result) => {
            let path = './uploaded_files/';
            return Promise.all(
                request.files.map((file) => {
                    let filename = path + Date.now() + '_' + file.originalname;
                    path = './garbage/';
                    console.log('Trying to save file:', filename);
                    return promiseWriteFile(filename, file.buffer, 'binary')
                        .then(() => {
                            return  { filename };
                        })
                        .catch((error) => {
                            return { error, filename };
                        });
                }),
            );
        })
        .then((results) => {
            const successes = results.filter(({ error }) => !error);
            const failures = results.filter(({ error }) => error);
            // process them here
        })
    

    现在您可以根据需要处理它们,并且每个对象在返回中都有一个文件名,让您可以识别它们,删除它们,随心所欲地使用它们。

    【讨论】:

    • 所以问题是在这里使用实际的throw。抛出错误会立即脱离大承诺链,杀死 promise.all 并将我带入最底部的 catch 块,但未解决的承诺仍在运行。那准确吗?我不确定我将如何转发错误而不抛出它。使用Promise.allSettled 然后遍历列表查找错误是否更合适?
    • 不,只需接受所有的承诺并返回一个带有消息或错误的对象。然后稍后处理所有退货。如果这是关键任务并且它必须是全有或全无,那么您也可以在此时删除成功的文件。请记住,promise 的返回是您想要的任何东西,并且 Promise.all 解析为一个数组。带有键消息或错误的同质对象数组很容易使用过滤器、映射等进行处理。
    • Promise.all().then((arrayOfReturns) =&gt; {...}) 是您想要研究的模式。
    • 谢谢,我认为这行得通,我现在就试试
    • @nullromo 更新了答案,并举例说明了如何做到这一点。
    【解决方案2】:

    当您在 promise.all 上使用 .catch 时,它将为您添加的每个子 Promise 添加 catch。没有“catchFinally”方法。

    要阻止其他承诺发生,请查看以下示例:

    Promise.config({ cancellation: true }); // <-- enables this non-standard feature
    
    const promise1 = new Promise((resolve, reject) => {
        setTimeout(resolve, 1000, 'resolve1');
    }).then(a => { console.log('then1'); return a; });
    
    const promise2 = new Promise((resolve, reject) => {
        setTimeout(reject, 2000, 'reject2');
    }).then(a => { console.log('then2'); return a; });
    
    const promise3 = new Promise((resolve, reject) => {
        setTimeout(resolve, 3000, 'resolve3');
    }).then(a => { console.log('then3'); return a; });
    
    const promises = [promise1, promise2, promise3];
    
    Promise.all(promises)
        .then(values => { 
            console.log('then', values); 
        })
        .catch(err => { 
            console.log('catch', err); 
            promises.forEach(p => p.cancel()); // <--- Does not work with standard promises
        });
    

    请注意,即使 promise3 被取消,它的 setTimeout 回调仍然会被调用。但它不会触发 then 或 catch 回调。就好像这个承诺永远不会成为一个决议......永远。

    【讨论】:

    • 我建议不要使用 Promise 的取消,因为它会使系统处于未知且可能不稳定的状态
    • @RobertMennell 我不同意。如果他有依赖于成功完成过去的承诺的承诺,那么取消下一个承诺是有意义的。
    • @RobertMennell 请提供有关“使系统处于未知状态”的更多信息。如果他知道他的应用程序可能具有未知状态,那么他也应该在 Promise 之外处理这些条件。
    • 如果是全部或全部,他们应该以串行方式处理它。否则,他们会提前将文件写入磁盘。如果这应该是一种全有或全无的方法,那么您想一次写入一个文件,如果有任何失败,您想删除成功写入的工件,然后您想记录。通过取消承诺链,您可能已经将文件写入磁盘,并且只取消了 .next() 步骤和日志。该文件可能仍然存在。
    猜你喜欢
    • 2016-05-16
    • 2018-03-18
    • 2016-05-16
    • 2015-10-06
    • 2018-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多