range.address выдает ошибки, связанные с контекстом

Мы занимаемся разработкой с использованием Excel JavaScript API уже несколько месяцев. Мы сталкивались с проблемами, связанными с контекстом, которые были решены по неизвестным причинам. Мы не смогли воспроизвести эти проблемы и задались вопросом, как они были решены. В последнее время подобные проблемы снова стали появляться. Ошибку мы постоянно получаем:

свойство "адрес" недоступно. Перед чтением значения свойства вызовите метод load для содержащего объекта и вызовите context.sync () для связанного контекста запроса.

Мы подумали, что, поскольку у нас есть несколько функций, определенных для модульного построения кода в проекте, возможно, контекст между этими функциями где-то отличается, что осталось незамеченным. Таким образом, мы пришли к единому контекстному решению, реализованному с помощью шаблона модуля JavaScript.

    var ContextManager = (function () {
        var xlContext;//single context for entire project/application.
        function loadContext() {
            xlContext = new Excel.RequestContext();
        }
        function sync(object) {
            return (object === undefined) ? xlContext.sync() : xlContext.sync(object);
        }
        function getWorksheetByName(name) {
            return xlContext.workbook.worksheets.getItem(name.toString());
        }
        //public
        return {
            loadContext: loadContext,
            sync: sync,
            getWorksheetByName: getWorksheetByName
        };
    })();

ПРИМЕЧАНИЕ: приведенный выше код сокращен. Добавлены и другие методы, обеспечивающие использование единого контекста во всем приложении. Однако при реализации единого контекста на этот раз мы смогли воспроизвести проблему.

    Office.initialize = function (reason) {
        $(document).ready(function () {
            ContextManager.loadContext();
            function loadRangeAddress(rng, index) {
                rng.load("address");
                ContextManager.sync().then(function () {
                    console.log("Address: " + rng.address);
                }).catch(function (e) {
                    console.log("Failed address for index: " + index);
                });
            }
            for (var i = 1; i <= 1000; i++) {
                var sheet = ContextManager.getWorksheetByName("Sheet1");
                loadRangeAddress(sheet.getRange("A" + i), i);//I expect to see a1 to a1000 addresses in console. Order doesn't matter.
            }
        });
    }

В приведенном выше случае только «A1» выводится на консоль как адрес диапазона. Я не вижу печатаемых других адресов (от A2 до A1000). Выполняется только блок catch. Кто-нибудь может объяснить, почему это происходит? Хотя я написал выше цикл for, это не мой вариант использования. В реальном случае возникают такие ситуации, когда один объект диапазона в функции a должен загрузить адрес диапазона. Между тем, другая функция b также хочет загрузить адрес диапазона. И функция a, и функция b работают асинхронно с отдельными задачами, например, одна создает объект таблицы (таблица требует адреса), а другие вставляют данные в лист (есть инструкция отладки, чтобы увидеть, куда были вставлены данные).

Это то, что наша команда не смогла выяснить или найти решение.


person sidnc86    schedule 23.02.2017    source источник


Ответы (1)


В этом коде многое упаковано, но проблема в том, что вы вызываете sync несколько раз, не дожидаясь предыдущей синхронизации.

С этим есть несколько проблем:

  • Если бы вы использовали разные контексты, вы бы фактически увидели, что существует ограничение в ~ 50 одновременных запросов, после чего вы получите ошибки.
  • In your case, you're running into a different (and almost opposite) problem. Given the async nature of the APIs, and the fact that you're not awaiting on the sync-s, your first sync request (which you'd think is for just A1) will actually contain all the load requests from the execution of the entire for loop. Now, once this first sync is dispatched, the action queue will be cleared. Which means that your second, third, etc. sync will see that there is no pending work, and will no-op, executing before the first sync ever came back with the values!
    • [This might be considered a bug, and I'll discuss with the team about fixing it. But it's still a very dangerous thing to not await the completion of a sync before moving on to the next batch of instructions that use the same context.]

Исправление - дождаться синхронизации. Это намного проще всего сделать в TypeScript 2.1 и его функции async/await, иначе вам нужно будет выполнить асинхронную версию цикла for, которую вы можете найти, но это довольно неинтуитивно (требуется создание сверхобещания, которое продолжает цепочку куча .then-ов)

Итак, ваш модифицированный код, основанный на TypeScript, будет

ContextManager.loadContext();
async function loadRangeAddress(rng, index) {
    rng.load("address");
    await ContextManager.sync().then(function () {
        console.log("Address: " + rng.address);
    }).catch(function (e) {
        OfficeHelpers.Utilities.log(e);
    });
}
for (var i = 1; i <= 1000; i++) {
    var sheet = ContextManager.getWorksheetByName("Sheet1");
    await loadRangeAddress(sheet.getRange("A" + i), i);//I expect to see a1 to a1000 addresses in console. Order doesn't matter.
}

Обратите внимание на async перед функцией loadRangeAddress и два await перед ContextManager.sync() и loadRangeAddress.

Обратите внимание, что этот код также будет работать довольно медленно, поскольку вы выполняете асинхронный обход для каждой ячейки. Это означает, что вы не используете пакетную обработку, которая лежит в основе объектной модели новых API.

Для полноты картины я должен также отметить, что создание «сырого» RequestContext вместо использования Excel.run имеет некоторые недостатки. Excel.run выполняет ряд полезных вещей, наиболее важной из которых является автоматическое отслеживание и отключение отслеживания объектов (здесь не актуально, поскольку вы только читаете данные; но было бы актуально, если бы вы загружали, а затем хотели записать обратно в объект).

Наконец, если я могу порекомендовать (полное раскрытие: я являюсь автором книги), вы, вероятно, найдете немало полезной информации о Office.js в электронной книге «Создание надстроек Office с использованием Office.js», доступно по адресу https://leanpub.com/buildingofficeaddins. В частности, в нем есть очень подробный (10-страничный) раздел о внутренней работе объектной модели («Раздел 5.5: Детали реализации, для тех, кто хочет знать, как это действительно работает»). Он также предлагает советы по использованию TypeScript, имеет общий учебник Promise / async-await, описывает, что .run делает, и содержит много дополнительной информации о OM. Кроме того, хотя он еще не доступен, он скоро предложит информацию о том, как возобновить, используя тот же контекст (с использованием более новой техники, чем та, которая была первоначально описана в Как можно использовать диапазон в разных контекстах Word.run?). Книга представляет собой малоизвестную «вечнозеленую» книгу, сынок, как только я напишу эту тему в ближайшие недели, обновление будет доступно всем существующим читателям.

Надеюсь это поможет!

person Michael Zlatkovsky - Microsoft    schedule 23.02.2017
comment
Я слышал, что Excel TypeScript теперь синхронный, а не асинхронный. Повлияет ли это на ваш код или упростит его? - person johny why; 09.04.2021
comment
@johnywhy, я покинул команду Extensibility более года назад, поэтому, если вы зададите отдельный вопрос, возможно, найдутся другие люди, которые смогут на него ответить. Да, скоро появится синхронный вариант API (docs .microsoft.com / en-us / office / dev / scripts / overview / excel), хотя я считаю, что эти API / возможности все еще находятся в предварительной версии, и я не уверен, можно ли заставить их работать в обычная надстройка или только внутри редактора кода автоматизации. Есть также соображения производительности, поскольку API синхронизации жертвует некоторыми характеристиками ради удобства использования. Так что на данный момент все вышесказанное по-прежнему актуально - person Michael Zlatkovsky - Microsoft; 09.04.2021
comment
Есть идеи, когда он выйдет из предварительного просмотра? Прошёл почти год. Спасибо - person johny why; 09.04.2021
comment
@johnywhy, я не знаю - и, как я уже сказал, я перешел из этой команды в другое место в Microsoft. Похоже, есть тег stackoverflow для офисных скриптов, я рекомендую вам задать здесь (и не стесняйтесь ссылаться на свой вопрос в комментариях здесь, чтобы другие могли следить за обсуждением): stackoverflow.com/questions/tagged/office-scripts - person Michael Zlatkovsky - Microsoft; 10.04.2021