简体   繁体   中英

Resolve any number promises nested inside of an object

I have an object with nested File instances in various locations. 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.

I have an existing functions that returns a Promise and resolves when the data URL from the file is ready.

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.

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.

I'm also aware of Promise.all but unsure how to use it in this context.

How can I return convertPhotosToBase64 as a promise that resolves when all of the files have been converted to base64 strings?

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. This means that the whole convertPhotosToBase64 will need to become asynchronous and always return a 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. 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.

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