Условия гонки с использованием Office.js в Excel

Я сталкиваюсь с каким-то состоянием гонки в следующем коде, где я пытаюсь записать ответ HTTP-запроса в активную ячейку. Я прочитал некоторые возможные решения ошибок «InvalidObjectPath» в Office.js (я использую ScriptLab специально), но я не думаю, что пытаюсь использовать что-либо в нескольких контекстах.

Текущее поведение иногда работает, но иногда в ячейку ничего не записывается.

var counter = 0;
$("#run").click(run);
async function run() {
    try {

        await Excel.run(async (ctx) => {
            var user; 
            const sUrl = "https://jsonplaceholder.typicode.com/users/1";
            var client = new HttpClient();
            var range = ctx.workbook.getSelectedRange(); 
            counter++;
            client.get(sUrl, function (response) {
                var obj = JSON.parse(response);
                user = obj.username;
                range.values = [[user + counter]];
                ctx.sync();
            });
            await ctx.sync();
        });
    }
    catch (error) {
        OfficeHelpers.UI.notify(error);
        OfficeHelpers.Utilities.log(error);
    }
}

var HttpClient = function() {
    this.get = function(aUrl, aCallback) {
        var anHttpRequest = new XMLHttpRequest();
        anHttpRequest.onreadystatechange = function() { 
            if (anHttpRequest.readyState == 4 && anHttpRequest.status == 200)
                aCallback(anHttpRequest.responseText);
        }
        anHttpRequest.open( "GET", aUrl, true );            
        anHttpRequest.send(null);
    }
}

person James Spotanski    schedule 14.06.2017    source источник


Ответы (1)


Проблема в том, что вы не ждете завершения client.get. Это означает, что [в некоторых случаях] Excel.run завершит и «соберет мусор» (иш) некоторые из объектов (range) до того, как будет выполнен обратный вызов внутри client.get.

Вы можете решить проблему несколькими способами:

  1. Выполните вызов веб-службы перед выполнением Excel.run. В вашем примере здесь (который может быть нереалистичным для многих других сценариев, но он здесь) вы на самом деле вообще не полагаетесь ни на что из документа, прежде чем выполнить свой веб-вызов. В этом случае вообще нет необходимости находиться внутри Excel.run, вы можете сделать Excel.run частью обратного вызова при вызове веб-службы.

  2. Оберните вызов веб-службы в обещание, чтобы его можно было дождаться. Примерно так: var HttpClient = function() { this.get = function(aUrl) { return new Promise(function (resolve, reject) { var anHttpRequest = new XMLHttpRequest(); anHttpRequest.onreadystatechange = function () { if (anHttpRequest.readyState == 4 && anHttpRequest.status == 200) { resolve(anHttpRequest.responseText); } else { reject(anHttpRequest.statusText); } } anHttpRequest.open("GET", aUrl, true); anHttpRequest.send(null); }); } }

Я описываю оба подхода (и многое другое ...) в книге, которую я писал о создании надстроек Office с использованием Office.js: https://leanpub.com/buildingofficeaddins/. Я вставляю ниже несколько скриншотов из некоторых материалов соответствующей книги.

Кстати, я должен сказать, что получение выбора - это один из немногих случаев, когда вы не хотите откладывать sync, поскольку вы хотите зафиксировать мимолетное выделение на определенный момент времени, а не то, что станет выделением через X секунд , после успешного веб-вызова. Так что это один из немногих случаев, когда вы можете захотеть вставить дополнительный await context.sync(), даже если он вам технически не нужен. См. Раздел «5.8.2: Когда синхронизировать» в книге для получения дополнительной информации.

=====

Обещание API:

Обещание API

=====

From about Promises:

From about Promises

=====

Из раздела сведений о реализации:

Из раздела сведений о реализации

person Michael Zlatkovsky - Microsoft    schedule 14.06.2017
comment
Не могли бы вы предоставить эти отрывки в качестве цитируемого текста вместо картин? - person Bergi; 14.06.2017
comment
Вам не нужно (и не следует) использовать конструктор new Promise для обещания вызова jQuery ajax. Просто сделайте return Promise.resolve($.ajax(…)).then(…, …) - jQuery уже возвращает thenable (вы можете даже await отложить jQuery напрямую) - person Bergi; 14.06.2017
comment
@Bergi, мне проще всего делать скриншоты, поскольку книга написана на диалекте Markdown (Markua), который используется LeanPub, но не всегда на 100% хорошо работает с GitHub. - person Michael Zlatkovsky - Microsoft; 14.06.2017
comment
@Bergi, за обещание вызова jQuery ajax: это правда, что jQuery возвращает обещание, хотя, к сожалению, это не полностью стандартное обещание (например, у него нет .catch). Поэтому я думаю, что для большей гибкости все еще есть смысл обернуть его стандартным обещанием, особенно когда я хочу выполнить некоторую пост-обработку ответа сервера, как я сделал выше. Однако в той же главе есть и другие примеры, такие как обещание функции setTimeout. - person Michael Zlatkovsky - Microsoft; 14.06.2017
comment
@ MichaelZlatkovsky-Microsoft Спасибо! В конечном итоге я хочу использовать значение из getActiveRange () для создания URL-адреса запроса, поэтому я буду обещать вызов веб-службы. Очень признателен! - person James Spotanski; 14.06.2017
comment
@ MichaelZlatkovsky-Microsoft Да, упаковка - это хорошо, но для этого нужно Promise.resolve вместо new Promise. Ваш фрагмент по существу использует конструктор Promise antipattern - и в нем отсутствует обработка ошибок кода внутри обратных вызовов done и fail. - person Bergi; 14.06.2017
comment
@Bergi, возможно, я просто неправильно это понимаю - но даже в присланной вами ссылке на конструктор антипаттерна я вижу такие вещи, как: «Вы должны использовать только отложенные объекты, когда конвертируете API в обещания и не можете делать это автоматически», или когда вы пишете функции агрегирования, которые проще выразить таким образом .. Из того, что я видел / читал об объекте jQuery Promise, поскольку это не полностью стандартный Promise (т.е. не поддерживает .catch), это кажется самым безопасным рассматривать его как необещающий API и заключить в конструктор Promise. Рад узнать больше, если вы не согласны. - person Michael Zlatkovsky - Microsoft; 15.06.2017
comment
@ MichaelZlatkovsky-Microsoft Не нужно поддерживать улов. Ему даже не нужно использовать then, который возвращает новое обещание. Он должен быть только доступным и вызывать соответствующий обратный вызов, и вы можете передать его Promise.resolve(…) в get вполне стандартное обещание. И даже если вы хотите обернуть его в конструктор обещаний, вам следует обернуть его напрямую (new Promise((res, rej) => $.ajax(…).then(res, rej)) или ….done(res).fail(rej)), вместо того, чтобы выполнять кучу вещей в этих нестандартных небезопасных обратных вызовах done и fail. - person Bergi; 15.06.2017