Рассмотрим последний промис в цепочке Resolve.all

Я хочу экспортировать HTML-контент со страниц Confluence. Они могут содержать теги <img> с атрибутами src, которые представляют собой обычные гиперссылки. Поскольку я тоже хочу их экспортировать, я решил заменить содержимое src соответствующими URL-адресами данных, чтобы было src = "data:image/png;base64,AEFJEFEF…".

Конечно, для этого требуется получение изображений через HTTP, и это можно сделать только асинхронно. Кроме того, он содержит множество «вложенных» асинхронных вызовов.

Это мой код:

    /**
     * @param {HTMLTableCellElement | undefined} cell
     */
    asynC#getCellHtml(cell) {
        if (!cell) return undefined;

        const srcMap = {}

        for await (const imgElement of cell.querySelectorAll('img')) {
            if ("attachment" !== imgElement.dataset.linkedResourceType) {
                return;
            }
            const imgUrl =
                new URL(imgElement.src, imgElement.dataset.baseUrl);
            await fetch(imgUrl)
                .then(response => response.blob())
                .then(blob => blob.arrayBuffer())
                .then(arrayBuffer => {
                    srcMap[imgElement.src] =
                        `data:${imgElement.dataset.linkedResourceContentType};base64,`
                        + Buffer.from(arrayBuffer).toString('base64');
                });
        }

        const cellHtml = cell.innerHTML;

        Object.entries(srcMap).forEach(([imgSrc, dataUrl]) => {
            cellHtml.replace(imgSrc, dataUrl)
        })

        return cellHtml;
    }

Для справки, такой HTML выглядит следующим образом:

<p style = "text-align: left;"><br/></p>
<p style = "text-align: left;"><span
        class = "confluence-embedded-file-wrapper confluence-embedded-manual-size"><img
        class = "confluence-embedded-image" draggable = "false" width = "639"
        src = "/confluence/download/attachments/2345432345/image-2024-7-11_16-48-22-1.png?version=1&amp;modificationDate=1720709302000&amp;api=v2"
        data-image-src = "/confluence/download/attachments/235432345/image-2024-7-11_16-48-22-1.png?version=1&amp;modificationDate=1720709302000&amp;api=v2"
        data-unresolved-comment-count = "0" data-linked-resource-id = "345654345"
        data-linked-resource-version = "1" data-linked-resource-type = "attachment"
        data-linked-resource-default-alias = "image-2024-7-11_16-48-22-1.png"
        data-base-url = "https://suite.acme.com/confluence"
        data-linked-resource-content-type = "image/png"
        data-linked-resource-container-id = "1491043790"
        data-linked-resource-container-version = "1" alt = ""/></span></p>
<p style = "text-align: left;"><br/></p>
<p style = "text-align: left;"><br/></p>

Моя цель — перебрать все элементы <img>, найти соответствующие теги <img>, получить данные их изображений и собрать массив замены. После этого я бы просто заменил все результаты соответствующими URL-адресами данных.

Я думаю, что мне бы хотелось чего-то вроде этого:

cell.querySelectorAll('img').map(cell => {
  // return a Promise that combines all the fetching etc.
  // so that it resolves() with returning the base64 string(!).
  return new Promise()…
});

После того, как я map()перенес этот массив в Promises, я мог Promise.all()и затем выполнить замену HTML.

Я понятия не имею, как «вернуть» это последнее обещание, после того как все остальные уже выполнены. Должен ли мой код использовать вызовы await, а не .then(), чтобы я не попадал в контекст обратного вызова?

«Я понятия не имею, как «вернуть» это последнее обещание, после того как все остальные уже выполнены». - Я не понимаю вопроса. Разве в вашем коде уже нет обещания? Теперь просто не делайте await это в цикле, а верните его из обратного вызова map.

Bergi 18.07.2024 14:15
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
1
64
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Несколько замечаний по вашему текущему коду

  1. for await (const imgElement of cell.querySelectorAll('img'): поскольку querySelectorAll нет async, вам не нужна for await (...) простая for (...) петля, все в порядке.

  2. if ("attachment" !== imgElement.dataset.linkedResourceType) { return; } выйдет из метода для первого элемента, не отвечающего этому условию, и оставит все остальные элементы необработанными. Более того, уже загруженные изображения не будут заменены, потому что вы никогда не доберетесь до кода после цикла. Используйте continue вместо return, чтобы пропустить текущий элемент и перейти к следующему элементу в списке.

  3. Не следует смешивать async/await с then/catch, если вы точно не знаете, что делаете. Потому что это вызовет путаницу и, возможно, приведет к неожиданному поведению.

При этом я бы реорганизовал ваш код следующим образом.

  1. Поскольку ваш asynC#getCellHtml(cell) асинхронный, я бы полностью переключился на await и отказался от всего .then(...)

  2. Замените цикл for, перебирающий все элементы, на Promise.all(). Вам действительно не нужен результат этого Promise.all, потому что, если он не выдаст, вы знаете, все обещания успешно решены. И поскольку каждый обратный вызов устанавливает соответствующее значение в объекте srcMap, вы знаете, что после разрешения Promise.all() все изображения будут загружены.

...

let srcMap =  {};

await Promise.all(cell.querySelectorAll('img').map(async c => {
  if ("attachment" !== c.dataset.linkedResourceType) {
    //ignore wrong resource types and do nothing
    return;
  };

  //for correct resourcetype load the images and update the `srcMap` object
  const 
    imgUrl = new URL(c.src, c.dataset.baseUrl),
    resp = await fetch(imgUrl),
    blob = await resp.blob(),
    buff = await blob.arrayBuffer();
  
  scrMap[c.src] = ...
});

const cellHtml = cell.innerHTML;

...

Конечно, этот код не имеет никакой обработки ошибок. Поэтому, если, например, одно изображение не загружается, весь процесс завершается. Но я позволю вам включить эту обработку ошибок в качестве упражнения.

ах да, спасибо за ваши замечания, 2) это была ошибка, так как изначально у меня был блок кода в .forEach(), поэтому return вместо continue. Также спасибо за подсказку, как не смешивать async/await с then/catch; и в целом за подробное объяснение. Это определенно помогает мне встать на правильный путь!

Adrian Föder 18.07.2024 16:33

Другие вопросы по теме