2017-02-23 43 views
3

我们已经使用Excel JavaScript API开发了好几个月了。我们遇到了与情况相关的问题,这些问题由于不明原因而得到解决。我们无法复制这些问题,并想知道他们如何解决问题。最近类似的问题又开始出现了。 我们始终得到的错误:range.address引发上下文相关错误

property'address'is not available。在读取该属性的值 之前,请在包含对象上调用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在单独的任务上异步工作,例如创建表对象(表需要地址)和其他数据到工作表(其中有调试语句来查看数据被粘贴到哪里)。

这是我们的团队无法弄清楚或找到解决方案。

回答

2

这个代码有很多内容,但是您遇到的问题是您在不等待以前的同步的情况下拨打同步很多次。

有几个问题是:

  • 如果您正在使用不同上下文,实际上会看到有50〜同时请求,之后,你会得到错误的限制。
  • 就你而言,你遇到了一个不同的(几乎相反的)问题。鉴于这些API的异步特性,以及您不在sync-s上等待的事实,您的第一个sync请求(您认为该请求仅用于A1)实际上将包含执行的所有load请求整个for循环。现在,一旦分配了第一个sync,操作队列将被清除。这意味着你的第二个,第三个等sync将看到没有待处理的工作,并且在第一个同步返回值之前将不执行任务,执行
    • [这可能被认为是一个错误,我将与团队讨论修复它。但它仍然是一个非常危险的事情就移动到下一个批次的使用相同的上下文指令之前不会等待一个sync完成。]

解决方法是等待同步。这是在TypeScript 2.1和它的async/await特性中最简单的做法,否则你需要做for循环的异步版本,你可以查看它,但这很不直观(需要创建一个超级保证来保持链接的.then -s一堆)

因此,修改后的打字稿,指明分数代码将

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功能的前面,这两个等待在前面 -s ContextManager.sync()loadRangeAddress

请注意,此代码也会运行得非常慢,因为您正在为每个单元格进行异步往返。这意味着你的不使用批处理,这是新API对象模型的核心。

为了完整起见,我还应该注意,创建“原始”RequestContext而不是使用Excel.run有一些缺点。 Excel.run做了许多有用的事情,其中​​最重要的是自动对象跟踪和未跟踪(这里不相关,因为你只是读回数据;但是如果你正在加载然后想要回写物体)。最后,如果我可以推荐(完全公开:我是本书的作者),你可能会在电子书“使用Office构建Office加载项”中找到关于Office.js的一些有用信息.js“,可在https://leanpub.com/buildingofficeaddins获得。具体来说,它有一个关于对象模型内部工作的非常详细的(10页)部分(“第5.5节:实现细节,对于那些想知道它是如何工作的人”)。它还提供了使用TypeScript的建议,有一个普通的Promise/async-await入门,描述了.run的功能,并且有更多关于OM的信息。此外,尽管尚未提供,但它将很快提供关于如何使用相同上下文(使用比最初在How can a range be used across different Word.run contexts?中描述的更新技术)恢复的信息。这本书是一本精简出版的“常青树”一书,儿子在我未来几周写这个话题时,所有现有读者都可以获得更新。

希望这会有所帮助!