Long operations crash Office addin (JS) - javascript

I just created a (JS) Word Add-in and found that long synchronous operations can make it crash. In these cases, the following error is displayed - [ADD-IN ERROR Sorry, we had to restart because this add-in wasn't responding.]
The following code is ran on a button click.
function scanText() {
Word.run(function (context) {
var body = context.document.body;
context.load(body, 'text');
return context.sync().then(function () {
var r = thisOperationCanTakeALongTimeIfDocIsLarge(body.text);
});
})
.catch(errorHandler);
}
How do I prevent this from happening? should I make the long operation asynchronous? How is this achieved in this context?

I have finally found a good way to solve this... I use a WebWorker like so:
function scanText() {
var w;
if (typeof (w) == "undefined") {
w = new Worker("./Scripts/myWebWorker.js");
}
else
{
showNotification("Sorry! No Web Worker support.");
}
w.onmessage = function (event) {
showNotification(event.data);
};
Word.run(function (context) {
var body = context.document.body;
context.load(body, 'text');
return context.sync().then(function () {
w.postMessage(body.text);
});
})
.catch(errorHandler);
}
And the myWebWorker.js file:
self.importScripts([...some scripts i need...]);
self.addEventListener("message", function (e) {
var r = thisOperationCanTakeALongTimeIfDocIsLarge(e.data);
postMessage(r);
}, false);

Related

chrome dev tools crashed during initialization call

The code below crashed on this line, document.body.appendChild(script);, where the new script gets added to the body element.The page crashed and browser's debugger tool showed this message DevTools was disconnected from the page. Once page is reloaded, DevTools will automatically reconnect. I'm not sure what is the error that caused the debugger tool to crash since the same process of adding and removing script to get random quote worked with #btn-quote click event. I've also tried to have the init function call when the page is loaded by replacing appController.init(); with window.onload = init;, but the same error still occurred. I would really appreciate with anyone can point out any logical error I made in the code below as I'm not able to figure them out.
var dataController = (function() {
var data = {
quote: '',
author: ''
}
function setRandomQuote() {
var script = document.createElement('script');
script.src = 'http://api.forismatic.com/api/1.0/?method=getQuote&lang=en&format=jsonp&jsonp=parseQuote';
document.body.appendChild(script);
window.parseQuote= function(json) {
data.quote = json.quoteText;
data.author = json.quoteAuthor;
}
script.parentNode.removeChild(script);
}
return {
getQuote: function() {
setRandomQuote();
return data;
}
};
})();
var UIController = (function(){
return {
changeQuote: function(data) {
document.querySelector('q').innerHTML = data.quote;
document.querySelector('cite').innerHTML = data.author;
}
}
})();
var appController = (function(dataCtrl, UICtrl) {
var setupEventListener = function() {
document.querySelector('#btn-quote').addEventListener('click', getRandomQuote);
};
function getRandomQuote() {
var data= {};
// 1. get quote
var data = dataCtrl.getQuote();
// 2. change UI with new quote
UICtrl.changeQuote(data);
}
return {
init: function() {
setupEventListener();
getRandomQuote();
}
}
})(dataController, UIController);
appController.init();

nodejs write file frequently

I have a listener to listen for the change of content, once the content modified, it will emit the handler function:
$('#editor').on('onchange', () => changeHandler('...','...'));
function changeHandler(filePath, content){
var ws = fs.createWriteStream(filePath, 'utf8');
ws.write(content);
}
My problem is that the 'onchange' occurs too often, so 'write file' too often handles, it may lost data during the period.
Can someone give any suggestion?
Update
Now I've changed code according the answers below looks like:
this.buffer = null; //used to cache
// once content changed, maybe too often
changeHandler() {
if (this.editor.curOp && this.editor.curOp.command.name) {
var id = $('.nav-items li.active .lk-hosts').attr('data-hosts-id');
var content = this.editor.getValue();
// cache data, not immediately write to file
this.buffer = {id: id, content: content};
}
}
setInterval(()=> {
// means there's data in cache
if (this.buffer !== null) {
let id = this.buffer.id;
let content = this.buffer.content;
// reset cache to null
this.buffer = null;
// write file
this.writeContent(id, content, (err)=> {
})
}
}, 800);
Thanks all answers!
Why not simply build a buffer to collect written text then write to file only when you have a certain number of writes:
$('#editor').on('onchange', () => changeHandler('...','...'));
var writeBuffer = ''; // can also make this an array
var writeBufferSize = 0;
var filePath = 'path_to_file';
var ws = fs.createWriteStream(filePath, 'utf8');
function changeHandler(content){
if (writeBufferSize == SOME_THRESHOLD) {
ws.write(writeBuffer);
writeBuffer = '';
writeBufferSize = 0;
} else {
writeBuffer += content + '\n';
writeBufferSize++;
}
}
If you choose a write buffer threshold that's too big, you might want to delegate the write to some worker thread to be done in parallel, and in this case you can create another temporary write buffer to fill out while the original is being written, then switch the two.
This sample below shows how to make debounced event handling, although it's not node.js code it's same in concept.
// eventEmitter variable to use
var emitter = new EventEmitter();
// dom element change event
$('#editor').on('input', function(event) {
emitter.emit('changeEvent', event.target.value);
});
// event listener, which debounces change event of input
emitter.on('changeEvent', debounce(function(data) {
writeFile('li', data);
}, 1000)); // <== debounce for 1second
// sample emitter, for demo
// we don't have access to nodejs EventEmitter class in Stackoverflow
// don't use in production
function EventEmitter() {
var callbacks = [];
return {
on: function(eventName, fn) {
callbacks.push({
eventName: eventName,
callback: fn
})
},
emit: function(eventName, payload) {
var fn = callbacks.find(function(item) {
return item.eventName === eventName;
});
if (fn) {
fn.callback(payload);
}
}
}
}
// simple logger for demo purpose
// emulates write file
function writeFile(name, content) {
var $elem = $(document.createElement(name));
$elem.text(content);
$('#logger').append($elem);
}
// throttle function - reduces fn call with timeout
// credits: https://remysharp.com/2010/07/21/throttling-function-calls
function debounce(fn, delay) {
var timer = null;
return function() {
var context = this,
args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="editor" placeholder="Enter text, this will emit change event"></textarea>
<p>
Notice the 1sec throttle (write something, pause for 1sec, write again)
</p>
<ul id="logger"></ul>
The debounce function can be also used on textarea change event
// debounce emitting
$('#editor').on('input', debounce(function(event) {
emitter.emit('changeEvent', event.target.value);
}, 1000));
// write file when received event without debounce
emitter.on('changeEvent', function(data){
logElement('li', data);
});
The Underscore library has _.throttle() and _.debounce() functions.

Calling add-on from web page in new multiprocess Firefox

dear all.
We have crypto signing extensions implemented for few browsers in our application, everything went fine, but now we faced problem with new Mozilla's multiprocess API migration (E10S aka Electrolysis).
Our web part interacts with extension which collaborates with native library written in C (we utilize c-types lib for this part).
Now Firefox is moving to multiprocess model that requires code adaptation. The most significant and complicated part for now is content-to-extension communication reimplementation. It was implemented according to related official documentation
We used bootstrap extension initialization in following manner:
function startup(params, reason) {
include("chrome/content/extmain.js");
mainWindow = winMediator.getMostRecentWindow("navigator:browser");
if (null == mainWindow) {
var windowListenerWidget = {
onOpenWindow: function (aWindow) {
winMediator.removeListener(windowListenerWidget);
var mainWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
mainWindow.addEventListener("load", function onWindowLoad() {
mainWindow.removeEventListener("load", onWindowLoad);
addAddonListener(mainWindow);
});
},
onCloseWindow: function (aWindow) {
},
onWindowTitleChange: function (aWindow, aTitle) {
}
};
winMediator.addListener(windowListenerWidget);
} else {
addAddonListener(mainWindow);
}
}
function addAddonListener(win) {
win.document.addEventListener(
"CryptoApiExtension_HandleMsg",
function (event) {
var node = event.target;
if (!node || node.nodeType != 3) {
return;
}
var response = CryptoApiExtension.handleMessage(JSON.parse(node.nodeValue));
var doc = node.ownerDocument;
node.nodeValue = JSON.stringify(response);
var event = doc.createEvent("HTMLEvents");
event.initEvent("CryptoApiExtension_response", true, false);
return node.dispatchEvent(event);
}, false, true);
}
This code above was broken with new multiprocess architecture. There are lot of documentation we have read, but still there's no way we could handle this issue.
The question is: how to adapt this code to make extension accept web page invocations?
You now need to use messageManagers and frame scripts for inter-process communication:
// bootstrap.js
function addAddonListener(win) {
win.messageManager.addEventListener(
"CryptoApiExtension_request",
function (event) {
var response = CryptoApiExtension.handleRequest(event.json);
var childMM = event.target.messageManager;
childMM.sendAsyncMessage("CryptoApiExtension_response", response);
}
);
// <...>
win.messageManager.loadFrameScript("chrome://myaddon/content/frame-script.js", true);
}
// frame-script.js
sendAsyncMessage("CryptoApiExtension_request", request);
addMessageListener(
"CryptoApiExtension_response",
function(event) {
handleResponse(event.json);
}
);

What's the best(right) way to write a polling method (with Typescript & AngularJS)?

I am trying to write a polling method that polls a server periodically to check whether a zip file has already been created or not.
What I want to accomplish are the following:
Calls(ajax) an API that creates a zip file on server
Calls(ajax) another API that checks if the zip file has already been created (polling method)
Some subsequent process
Here is my code snippet ↓
var success: boolean = false;
//1. requests a server to create a zip file
this.apiRequest.downloadRequest(params,ApiUrl.URL_FOR_DOWNLOAD_REQUEST)
.then((resObj) => {
var apiRes: IDownloadService = resObj.data;
if (apiRes.status[0].statusCode == "000") {
success = true;
} else {
//Error
}
}).then(() => {
if (success) {
//2. polls the server to check if the zip file is ready
<- Polling method↓ ->
this.polling(params).then((zipUrl) => {
console.log(zipUrl); //always logs zipUrl
//some subsequent process...
});
}
});
Could anyone give some examples of polling method that would work in this case?
Added:
private polling(params: any): ng.IPromise<any> {
var poller = () => this.apiRequest.polling(params, ApiUrl.URL_FOR_POLLING);
var continuation = () => poller().then((resObj) => {
var apiRes: IDownloadService = resObj.data;
if (apiRes.zipFilePath == "") {
return this.$timeout(continuation, 1000);
} else {
return apiRes.zipFilePath;
}
})
var result: ng.IPromise<any> = continuation();
return result;
}
Basically abstract the methods out as shown below:
let poll = () => this.apiRequest.downloadRequest(params,ApiUrl.URL_FOR_DOWNLOAD_REQUEST)
let continuation = () => poll().then((/*something*/)=> {
/*if still bad*/ return continuation();
/*else */ return good;
})
continuation().then((/*definitely good*/));
Update
As requested in the comment below:
return this.$timeout(continuation, 1000);
This is needed to get angular to kick off a digest cycle.

JavaScript Metro app background task error

I'm trying to create a background task for my JavaScript Metro app. I added these to the default.js file:
function RegisterBackgroundTask(taskEntryPoint, taskName, trigger, condition) {
// Check for existing registrations of this background task.
var taskRegistered = false;
var background = Windows.ApplicationModel.Background;
var iter = background.BackgroundTaskRegistration.allTasks.first();
var hascur = iter.hasCurrent;
while (hascur) {
var cur = iter.current.value;
if (cur.name === taskName) {
taskRegistered = true;
break;
}
hascur = iter.moveNext();
}
// If the task is already registered, return the registration object.
if (taskRegistered == true) {
return iter.current;
}
// Register the background task.
var builder = new background.BackgroundTaskBuilder();
builder.Name = taskName;
builder.TaskEntryPoint = taskEntryPoint;
builder.setTrigger(trigger);
if (condition != null) {
builder.addCondition(condition);
}
var task = builder.register();
return task;
}
var trigger = new Windows.ApplicationModel.Background.SystemTrigger(Windows.ApplicationModel.Background.SystemTriggerType.timeZoneChange, false);
RegisterBackgroundTask("js\\bgtask.js", "test", trigger);
This is my bgtask.js:
(function () {
"use strict";
var backgroundTaskInstance = Windows.UI.WebUI.WebUIBackgroundTaskInstance.current;
function doWork() {
// Write JavaScript code here to do work in the background.
console.log("task done");
close();
}
doWork();
})();
This is my app manifest:
When I change the timezone, nothing happened. I checked the event log and there is an error:
The background task with entry point and name failed to activate with error code 0x80040154.
What did I do wrong?
builder.Name = taskName;
builder.TaskEntryPoint = taskEntryPoint;
should be
builder.name = taskName;
builder.taskEntryPoint = taskEntryPoint;

Categories