【问题标题】:Promise.all() fires before promise arrays are fully populatedPromise.all() 在 Promise 数组完全填充之前触发
【发布时间】:2017-01-29 20:59:38
【问题描述】:

我正在尝试实现多个异步调用,并且只有在全部完成后才继续。

过程是这样的,我需要按以下顺序完成: 获取文件路径列表, 将它们复制到新的目的地, 转换位图, 创建缩略图, 将信息添加到数组中以批量插入数据库

为此,我使用了 Promise 数组和 Promise.all() 方法。此方法触发得太早,因为它在承诺数组尚未(完全)填充时执行。 如果我在 for 循环之后延迟一段时间,它会起作用,但所需的时间取决于文件的数量。

这里是主要调用:

queueFiles(files).then(function () {
    // Add to DB
    console.log(current_import.sql.length); // Number of all datasets (0 at runtime)
    console.log(current_import.sql);        // An array of datasets   ([] at runtime)
});

这是没有按预期工作的功能:

let import_promises = {
    copy_promises : [],
    convert_promises : [],
    thumbnail_promises : [],
    sql_promises : []
};

function queueFiles(files) {
    return new Promise(function (resolve, reject) {
        try {
            for (file of files) {
                let original_filename = file.split("\\").pop();
                let original_extension = original_filename.split(".").pop();

                let hashed_name = crypto.createHmac('sha256', secret).update(original_filename).digest('hex') + "." + original_extension;
                let new_path = data_folder + "/" + hashed_name;

                import_promises.copy_promises.push(fs.copy(file, new_path).then(function () {
                    if (original_extension == "bmp") {
                        import_promises.convert_promises.push(convertFile(new_path).then(function (result) {
                            import_promises.thumbnail_promises.push(createThumbnail(result.path, thumb_folder + "/" + result.path.split("/").pop(), 500).then(function () {
                                import_promises.sql_promises.push(addSql(result.data));
                            }));
                        }));
                    } else {
                        import_promises.thumbnail_promises.push(createThumbnail(new_path, thumb_folder + "/" + hashed_name, 500).then(function () {
                            import_promises.sql_promises.push(addSql(new_path));
                        }));
                    }
                }));
            }

            Promise.all([import_promises.copy_promises, import_promises.convert_promises, import_promises.thumbnail_promises, import_promises.sql_promises])
                .then(() => {
                    // All files copied, converted, thumbnails created and prepared to insert into DB
                    resolve();
                });
        }
        catch (err) {
            reject(err);
        }
    });
}

这些是辅助函数:

function createThumbnail(input, output, width) {
    return new Promise(function (resolve, reject) {
        try {
            gm(input)
                .scale(width)
                .write(output, function (err) {
                    if (err) {
                        throw(err);
                    }
                    resolve();
                });
        }
        catch (err) {
            reject(err);
        }
    });
}

function convertFile(newPath) {
    return new Promise(function (resolve, reject) {
        try {
            let convertedPath = newPath.replace(/\.[^/.]+$/, "") + ".png";

            execFile("convert", [newPath, convertedPath]).then(function () {
                fs.unlinkSync(newPath);
                resolve({path: convertedPath});
            });
        }
        catch (err) {
            reject(err);
        }
    });
}

function addSql(data) {
    return new Promise(function (resolve, reject) {
        try {
            current_import.sql.push(data);

            current_import.done++;
            updateProgressBar();

            resolve();
        }
        catch (err) {
            reject(err);
        }
    });
}

编辑的代码(工作):

function createThumbnail(input, output, width) {
    return new Promise(function (resolve, reject) {
        gm(input)
            .scale(width)
            .write(output, function (err) {
                if (err) {
                    return reject(err);
                }
                resolve();
            });
    });
}

function convertFile(newPath) {
    let convertedPath = newPath.replace(/\.[^/.]+$/, "") + ".png";

    return execFile("convert", [newPath, convertedPath]).then(function () {
        fs.unlinkSync(newPath);
        return convertedPath;
    });
}

function addSql(data) {
    current_import.sql.push(data);

    current_import.done++;
    updateProgressBar();
}

function queueFiles(files) {
    return Promise.all(files.map(function (file) {
        let original_filename = file.split("\\").pop();
        let original_extension = original_filename.split(".").pop();

        let hashed_name = crypto.createHmac('sha256', secret).update(original_filename).digest('hex') + "." + original_extension;
        let new_path = data_folder + "/" + hashed_name;

        return fs.copy(file, new_path).then(function () {
            if (original_extension == "bmp") {
                return convertFile(new_path)
                    .then(function (path) {
                        return {path: path, hash: path.split("/").pop()};
                    });
            } else {
                return {path: new_path, hash: hashed_name}
            }
        }).then(function (result) {
            let outPath = thumb_folder + "/" + result.hash;
            return createThumbnail(result.path, outPath, 500)
                .then(function () {
                    return outPath;
                });
        }).then(function (thumb_path) {
            return addSql(thumb_path);
        });
    }));
}

【问题讨论】:

标签: javascript node.js ecmascript-6 promise


【解决方案1】:

首先,convertFilesqueueFiles 不需要 Promise 构造函数

但问题是......import_promises.copy_promises 是一个承诺数组,import_promises.sql_promises 是一个承诺数组,import_promises.convert_promises 是一个承诺数组......所以,您正在执行 Promise.all承诺数组数组 ... Promise.all 需要一个承诺数组

修改

Promise.all([import_promises.copy_promises, import_promises.convert_promises, import_promises.thumbnail_promises, import_promises.sql_promises]).then ...

Promise.all([].concat(import_promises.copy_promises, import_promises.convert_promises, import_promises.thumbnail_promises, import_promises.sql_promises).then ...

至于anipatterns,converFiles可以很简单

function convertFile(newPath) {
    let convertedPath = newPath.replace(/\.[^/.]+$/, "") + ".png";

    return execFile("convert", [newPath, convertedPath]).then(function () {
        fs.unlinkSync(newPath);
        resolve({path: convertedPath});
    });
}

addsql 甚至不是异步的,所以它为什么返回一个承诺是一个谜 -

queueFiles(无需深入研究那里的其他一些问题)可以

function queueFiles(files) {
    for (file of files) {
        let original_filename = file.split("\\").pop();
        let original_extension = original_filename.split(".").pop();

        let hashed_name = crypto.createHmac('sha256', secret).update(original_filename).digest('hex') + "." + original_extension;
        let new_path = data_folder + "/" + hashed_name;

        import_promises.copy_promises.push(fs.copy(file, new_path).then(function () {
            if (original_extension == "bmp") {
                import_promises.convert_promises.push(convertFile(new_path).then(function (result) {
                    import_promises.thumbnail_promises.push(createThumbnail(result.path, thumb_folder + "/" + result.path.split("/").pop(), 500).then(function () {
                        import_promises.sql_promises.push(addSql(result.data));
                    }));
                }));
            } else {
                import_promises.thumbnail_promises.push(createThumbnail(new_path, thumb_folder + "/" + hashed_name, 500).then(function () {
                    import_promises.sql_promises.push(addSql(new_path));
                }));
            }
        }));
    }

    return Promise.all([import_promises.copy_promises, import_promises.convert_promises, import_promises.thumbnail_promises, import_promises.sql_promises]);
}

在尝试理解流程之后,我想出了这个,我唯一不明白的部分是 addSql 函数,为什么它是一个承诺,

function convertFile(newPath) {
    return execFile("convert", [newPath, convertedPath]).then(function () {
        fs.unlinkSync(newPath);
        return convertedPath;
    });
}

function createThumbnail(input, output, width) {
    return new Promise(function(resolve, reject) {
        gm(input)
        .scale(width)
        .write(output, function (err) {
            if (err) {
                return reject(err);
            }
            resolve();
        });
    });
}

function addSql(data) {
    current_import.sql.push(data);

    current_import.done++;
    updateProgressBar();
}

function queueFiles(files) {
    return Promise.all(files.map(function(file) {
        return fs.copy(file, new_path).then(function () {
            if (original_extension == "bmp") {
                return convertFile(new_path)
                .then(function(path) {
                    return {path: path, hash: path.split("/").pop()};
                });
            } else {
                return {path: new_path, hash: hashed_name}
            }
        }).then(function (result) {
            return createThumbnail(result.path, thumb_folder + "/" + result.hash, 500)
            .then(function() {
                return result.path;
            });
        }).then(function(result) {
            return addSql(result);
        });

    }));
}

【讨论】:

  • 谢谢。通过此更改,我没有得到空数组的直接结果,但仍然只是部分数据。我也不明白如何更改 convertFiles 和 queueFiles 方法。我应该返回函数调用而不是创建 Promise 吗?
  • 我刚刚编辑了答案......数组的迷宫,推入了一个返回值被推入的函数......它正在努力弄清楚你需要等待什么 -看来您没有使用 Promises 最好的功能之一,那就是链接
  • 我对 Promise 以及如何利用它们来构建代码不熟悉。我只需要我的问题中描述的顺序。需要先复制文件才能进行转换。需要先转换它们才能创建缩略图,完成后我可以将包含缩略图路径的信息插入到数组中。
  • 我试过了,但我仍然得到立即返回的承诺,导致一个空数组。对于功能,它可以满足预期。对于 addSql 方法,我想我只是不确定自己在做什么。我编辑了我的问题以显示当前代码(需要进行一些小的更改)。
  • 对不起。在重写中,我错过了 fs.copy 之前的返回
猜你喜欢
  • 1970-01-01
  • 2018-04-05
  • 2016-07-07
  • 2023-03-15
  • 1970-01-01
  • 2014-11-15
  • 2020-12-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多