Я встречаю какое-то состояние гонки в следующем коде, где я пытаюсь написать ответ 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);
}
}
Проблема в том, что вы не ожидаете завершения client.get
. Это означает, что [некоторое время], Excel.run
завершит и "собирает мусор" (ish) некоторые из объектов (range
) до выполнения обратного вызова внутри client.get
.
Вы можете решить проблему несколькими способами:
Excel.run
вызов веб-службы перед выполнением Excel.run
. В вашем примере здесь (что может быть нереалистичным для многих других сценариев, но оно здесь), вы фактически не полагаетесь ни на что из документа, прежде чем выполнять свой веб-вызов. В этом случае вам вообще не нужно находиться внутри Excel.run
, вы можете иметь Excel.run
, Excel.run
частью обратного вызова в вызове веб-службы.
Оберните свой веб-сервисный вызов в Promise, чтобы его можно было ждать. Что-то вроде этого: 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); }); } }
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.js: https://leanpub.com/buildingofficeaddins/. Я вставляю несколько скриншотов из некоторых соответствующих материалов.
Кстати, я должен сказать, что получение выбора является одним из нескольких случаев, когда вы не хотите задерживать sync
, так как вы хотите захватить мимолетный выбор по времени, а не то, что станет выбором через X секунд, как только веб-вызов будет успешным. Таким образом, это один из немногих случаев, когда вам может понадобиться вставить дополнительный await context.sync()
даже если вам это не требуется. Дополнительную информацию см. В разделе "5.8.2: Когда синхронизировать" в книге.
=====
Продвижение API:
=====
От приблизительно обещаний:
=====
В разделе сведений о реализации:
new Promise
конструкторnew Promise
для «обещания» вызова jQuery ajax. Простоreturn Promise.resolve($.ajax(…)).then(…, …)
- jQuery уже возвращает таблицу (вы могли бы дажеawait
отложенную jQuery напрямую)