range.address 抛出与上下文相关的错误
range.address throws context related errors
我们已经使用 Excel JavaScript API 开发了好几个月了。我们一直遇到与上下文相关的问题,这些问题由于未知原因而得到解决。我们无法重现这些问题,想知道它们是如何解决的。最近又开始出现类似的问题。
我们一直得到的错误:
property 'address' is not available. Before reading the property's
value, call the load method on the containing object and call
"context.sync()" on the associated request context.
我们认为,因为我们定义了多个函数来模块化项目中的代码,所以这些函数之间的上下文可能有所不同,但没有引起注意。所以我们提出了通过 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 在单独的任务上异步工作,例如一个创建 table 对象(table 需要地址)和其他将数据粘贴到 sheet(有调试语句来查看数据粘贴到哪里).
这是我们团队未能弄清楚或找到解决方案的问题。
此代码包含很多内容,但您遇到的问题是您在不等待上一次同步的情况下多次调用 sync。
这有几个问题:
- 如果您使用的是不同的 上下文,您实际上会看到并发请求的限制约为 50 个,超过此限制后您将收到错误。
- 在您的情况下,您 运行 遇到了一个不同的(几乎相反的)问题。鉴于 API 的异步性质,以及您没有在
sync
-s 上等待的事实,您的第一个 sync
请求(您认为只是针对 A1)实际上将包含所有load
请求来自整个 for
循环的执行。现在,一旦第一个 sync
被调度,动作队列将被清除。这意味着您的第二个、第三个等 sync
将看到没有待处理的工作,并且不会执行任何操作,在第一次同步返回值 [=58= 之前执行 ]!
- [这可能被认为是一个错误,我将与团队讨论修复它。但是在继续使用相同上下文的下一批指令之前不等待
sync
完成仍然是一件非常危险的事情。]
解决方法是等待同步。这无疑是 TypeScript 2.1 及其 async/await
功能中最简单的操作,否则您需要执行 for 循环的异步版本,您可以查找它,但它相当不直观(需要创建一个 uber-承诺不断链接一堆 .then
-s)
因此,您修改后的 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.
}
注意loadRangeAddress函数前面的async,以及ContextManager.sync()
和前面的两个await-s loadRangeAddress
.
请注意,此代码也会 运行 相当缓慢,因为您要为每个单元格进行异步往返。这意味着您没有使用批处理,这是新 API 对象模型的核心。
为了完整起见,我还应该注意创建 "raw" RequestContext
而不是使用 Excel.run
有一些缺点。 Excel.run
做了很多有用的事情,其中最重要的是自动对象跟踪和取消跟踪(这里不相关,因为你只是读回数据;但如果你正在加载然后想要写回对象)。
最后,如果我可以推荐(完全公开:我是这本书的作者),您可能会在电子书中找到一些关于 Office.js 的有用信息 "Building Office Add-ins using Office.js" ,可在 https://leanpub.com/buildingofficeaddins. In particular, it has a very detailed (10-page) section on the internal workings of the object model ("Section 5.5: Implementation details, for those who want to know how it really works"). It also offers advice on using TypeScript, has a general Promise/async-await
primer, describes what .run
does, and has a bunch more info about the OM. Also, though not available yet, it will soon offer information on how to resume using the same context (using a newer technique than what was originally described in ) 获得。这本书是一本精简出版的 "evergreen" 书,儿子,一旦我在接下来的几周内写完这个主题,所有现有读者都会收到更新。
希望对您有所帮助!
我们已经使用 Excel JavaScript API 开发了好几个月了。我们一直遇到与上下文相关的问题,这些问题由于未知原因而得到解决。我们无法重现这些问题,想知道它们是如何解决的。最近又开始出现类似的问题。 我们一直得到的错误:
property 'address' is not available. Before reading the property's value, call the load method on the containing object and call "context.sync()" on the associated request context.
我们认为,因为我们定义了多个函数来模块化项目中的代码,所以这些函数之间的上下文可能有所不同,但没有引起注意。所以我们提出了通过 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 在单独的任务上异步工作,例如一个创建 table 对象(table 需要地址)和其他将数据粘贴到 sheet(有调试语句来查看数据粘贴到哪里).
这是我们团队未能弄清楚或找到解决方案的问题。
此代码包含很多内容,但您遇到的问题是您在不等待上一次同步的情况下多次调用 sync。
这有几个问题:
- 如果您使用的是不同的 上下文,您实际上会看到并发请求的限制约为 50 个,超过此限制后您将收到错误。
- 在您的情况下,您 运行 遇到了一个不同的(几乎相反的)问题。鉴于 API 的异步性质,以及您没有在
sync
-s 上等待的事实,您的第一个sync
请求(您认为只是针对 A1)实际上将包含所有load
请求来自整个for
循环的执行。现在,一旦第一个sync
被调度,动作队列将被清除。这意味着您的第二个、第三个等sync
将看到没有待处理的工作,并且不会执行任何操作,在第一次同步返回值 [=58= 之前执行 ]!- [这可能被认为是一个错误,我将与团队讨论修复它。但是在继续使用相同上下文的下一批指令之前不等待
sync
完成仍然是一件非常危险的事情。]
- [这可能被认为是一个错误,我将与团队讨论修复它。但是在继续使用相同上下文的下一批指令之前不等待
解决方法是等待同步。这无疑是 TypeScript 2.1 及其 async/await
功能中最简单的操作,否则您需要执行 for 循环的异步版本,您可以查找它,但它相当不直观(需要创建一个 uber-承诺不断链接一堆 .then
-s)
因此,您修改后的 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.
}
注意loadRangeAddress函数前面的async,以及ContextManager.sync()
和前面的两个await-s loadRangeAddress
.
请注意,此代码也会 运行 相当缓慢,因为您要为每个单元格进行异步往返。这意味着您没有使用批处理,这是新 API 对象模型的核心。
为了完整起见,我还应该注意创建 "raw" RequestContext
而不是使用 Excel.run
有一些缺点。 Excel.run
做了很多有用的事情,其中最重要的是自动对象跟踪和取消跟踪(这里不相关,因为你只是读回数据;但如果你正在加载然后想要写回对象)。
最后,如果我可以推荐(完全公开:我是这本书的作者),您可能会在电子书中找到一些关于 Office.js 的有用信息 "Building Office Add-ins using Office.js" ,可在 https://leanpub.com/buildingofficeaddins. In particular, it has a very detailed (10-page) section on the internal workings of the object model ("Section 5.5: Implementation details, for those who want to know how it really works"). It also offers advice on using TypeScript, has a general Promise/async-await
primer, describes what .run
does, and has a bunch more info about the OM. Also, though not available yet, it will soon offer information on how to resume using the same context (using a newer technique than what was originally described in
希望对您有所帮助!