简体   繁体   English

解决嵌套在对象内部的任何数量的Promise

[英]Resolve any number promises nested inside of an object

I have an object with nested File instances in various locations. 我有一个在各个位置都有嵌套File实例的对象。 I would like to recursively go through the object, check if an object is an instanceof File , use a promise to create a data url from the instance, and resolve the promise only when all of the promises have been resolved. 我想递归地遍历对象,检查对象是否为instanceof Fileinstanceof File ,使用promise从实例创建数据URL,并仅在所有promise都已解决时才解析promise。

I have an existing functions that returns a Promise and resolves when the data URL from the file is ready. 我有一个现有函数,该函数返回Promise并在文件中的数据URL准备就绪时进行解析。

export const parsePhoto = (file) => {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();

        try {
            reader.readAsDataURL(file);

            reader.onloadend = () => {
                return resolve(reader.result);
            }
        } catch(e) {
            console.warn('Could not upload photo', e.target);
        }
    })
}

I have a function to recursively look for a File in the object. 我有一个函数可以在对象中递归查找File

export const convertPhotosToBase64 = (values) => {
    if (!values) return values;

    const converted = Object.keys(values).reduce((acc, key) => {
        if (values[key] instanceof File) {
            // Do something here
            acc[key] = parsePhoto(values[key]);
        }

        if (isArray(values[key])) {
            acc[key] = values[key].map(value => {
                if (typeof value === 'object' && !isArray(value)) {
                    return convertPhotosToBase64(value);
                }

                return value;
            })
        }

        // Recurse if object
        if (typeof values[key] === 'object' && !isArray(values[key])) {
            acc[key] = convertPhotosToBase64(values[key]);
        }

        return acc;
    }, values);

    return converted;
}

I want to keep the existing structure of the object passed ( values ) and only replace the File instances with the base64 string. 我想保留传递的对象的现有结构( values ),仅用base64字符串替换File实例。

I'm also aware of Promise.all but unsure how to use it in this context. 我也知道Promise.all但不确定如何在这种情况下使用它。

How can I return convertPhotosToBase64 as a promise that resolves when all of the files have been converted to base64 strings? 所有文件都已转换为base64字符串时,如何返回convertPhotosToBase64作为可解决的承诺?

Let's first simplify your function a bit, to reduce the duplication of all those conditions: 让我们首先简化一下功能,以减少所有这些条件的重复:

export function convertPhotosToBase64(value) {
    if (typeof value !== 'object') return value;

    if (value instanceof File) return parsePhoto(value);

    if (isArray(value)) return value.map(convertPhotosToBase64);

    return Object.keys(value).reduce((acc, key) => {
        acc[key] = convertPhotosToBase64(value[key]);
        return acc;
    }, {});
}

Now, parsePhoto is asynchronous and returns a promise. 现在, parsePhoto是异步的并返回一个parsePhoto This means that the whole convertPhotosToBase64 will need to become asynchronous and always return a promise. 这意味着整个convertPhotosToBase64将需要变得异步并始终返回promise。 Given the four clearly distinct cases, that's actually simpler than it sounds: 考虑到四种明显不同的情况,这实际上比听起来简单:

export function convertPhotosToBase64(value) {
    // wrap value
    if (typeof value !== 'object') return Promise.resolve(value);

    // already a promise
    if (value instanceof File) return parsePhoto(value);

    // map creates all the promises in parallel, use `Promise.all` to await them
    if (isArray(value)) return Promise.all(value.map(convertPhotosToBase64));

    // chain one after the other
    return Object.keys(value).reduce((accP, key) =>
        accP.then(acc =>
            convertPhotosToBase64(value[key]).then(res => {
                acc[key] = res;
                return acc;
            })
        )
    , Promise.resolve({}));
}

If you are ok with doing everything in parallel (not only the arrays), you can also simplify the last case to 如果可以并行进行所有操作(不仅限于数组),还可以将最后一种情况简化为:

    return Object.keys(value).reduce((accP, key) =>
        Promise.all([accP, convertPhotosToBase64(value[key])]).then([acc, res] => {
            acc[key] = res;
            return acc;
        })
    , Promise.resolve({}));

or maybe better 也许更好

    const keys = Object.keys(value);
    return Promise.all(keys.map(key => convertPhotosToBase64(value[key])).then(results => {
        const acc = {};
        for (const [key, i] of keys.entries())
            acc[key] = results[i];
        return acc;
    });

Promise.all does what you want it to. Promise.all您的要求。 The easiest way to use it in this situation is by doing return Promise.all(converted) at the bottom of your function, which will return a special promise that doesn't resolve until all the promises in the argument have resolved. 在这种情况下使用它的最简单方法是在函数底部执行return Promise.all(converted) ,它将返回一个特殊的promise,该return Promise.all(converted)必须在参数中的所有promise都解决后才能解决。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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