-
Notifications
You must be signed in to change notification settings - Fork 2
WaitForSpoolingComplete
WaitForSpoolingComplete for ScriptX.Addon works because it is binary code with access to the browser and the OS - it can process the Windows message pump while the javascript thread from which it was called is halted but the browser remains responsive. This is super convenient and allows this commonly seen sequence:
factory.printing.Print(false);
WaitForSpoolingComplete();
self.close();
or,
for (i=0; i<5; i++) {
factory.printing.printHtml(docUrl + i + ".aspx");
}
WaitForSpoolingComplete();
We need to emulate the behaviour of WaitForSpoolingComplete() but 'modern' javascript with async/await cannot be used (this article illustrates a more 'modern' approach) as it would/may require significant code rewrite and/or additional tooling in the development environment.
The purpose of the ScriptX.Print.Client library is to allow current code to "live on" with, perhaps, a very small number of non-major changes to the code.
The single threaded nature of javascript means it is unacceptable to perform operations that block the thread. Synchronous AJAX calls are deprecated and their use results in a warning message on the console. When they will be removed is unknown, but it is a risky strategy to rely upon them being available. ScriptX.Print.Client library uses AJAX calls to initiate a print and polling AJAX calls to monitor the spooling status of the job. Some prints can take take minutes or more - making these calls synchronous could mean locking the browser for several minutes.
The solution is to use the foundation on which modern async/await is built but pared back to its more raw form - i.e. Promises / callbacks.
The idea is that a sequence of code that follows a WaitForSpoolingComplete() can be wrapped into a code block with { } and a little syntactic sugar added to the WaitForSpoolingComplete() call. The business logic code does not change.
So, this trivial example:
factory.printing.Print(false);
factory.printing.WaitForSpoolingComplete();
self.close();
becomes
factory.printing.Print(false);
MeadCo.ScriptX.WaitForSpoolingComplete().finally(function() {
self.close();
})
Note that there is a one line change. The remaining code is substantively unchanged.
There are a number of common scenarios in which the WaitForSpoolingComplete function is used to create synchronous code. Approaches to updating that code are discussed next.
Printing may be called from many points in the code and so is wrapped in a single function:
function printReport(printSettings) {
factory.printing.printer = printSettings.printerName;
factory.printing.header = printSettings.header;
factory.printing.footer = "";
factory.printing.Print(false);
factory.printing.WaitForSpoolingComplete();
}
The synchronicity of this function is made use of by the callers ... to echo the above trivial example:
printReport()
self.close()
Updating this code, requires that the printReport() function returns a promise ...
function printReport(printSettings) {
factory.printing.printer = printSettings.printerName;
factory.printing.header = printSettings.header;
factory.printing.footer = "";
factory.printing.Print(false);
return MeadCo.ScriptX.WaitForSpoolingComplete();
}
The calling code then changes to:
printReport.finally(function() {
self.close();
}
var ps = printSettings;
if ( condition1 ) {
printReport(ps);
// post printing action
postPrint1();
postPrint2();
if ( condition2 ) {
ps.header = "new header";
printReport();
postPrint3();
}
}
changes to:
var ps = printSettings;
if ( condition1 ) {
printReport(ps).finally(function() {
// post printing action
postPrint1();
postPrint2();
if ( condition2 ) {
ps.header = "new header";
printReport().finally(function() {
postPrint3();
});
}
});
}
2 examples. The first is easier when there is no tail processing after the loop.
function printReports() {
for(i=0; i<10; i++) {
if ( shouldPrintReport(i) ) {
var ps = { header: "Report: " + i };
printReport(ps);
postPrinting1();
postPrinting2();
}
}
// no code relying on synchronous behaviour here makes it easier.
}
...
printReports();
// no code relying on synchronous behaviour here.
becomes
function printReports(iStart) {
iStart = (typeof iStart !== 'undefined') ? iStart : 0;
for(i=iStart; i<10; i++) {
if ( shouldPrintReport(i) ) {
var ps = { header: "Report: " + i };
printReport(ps).finally(function() {
postPrinting1();
postPrinting2();
printReports(i+1);
});
return;
}
}
// no code relying on synchronous behaviour here makes it easier.
}
...
printReports();
// no code relying on synchronous behaviour here.
// it would execute before reports are complete.
The second example shows tail processing and this requires a larger number of changes but the core logic code does not change.
function printReports() {
showBusyUI();
for(i=0; i<10; i++) {
if ( shouldPrintReport(i) ) {
var ps = { header: "Report: " + i };
printReport(ps);
postPrinting1();
postPrinting2();
}
}
// code relying on synchronous behaviour here.
HideBusyUI();
}
...
printReports();
// also code relying on synchronous behaviour here.
becomes
function printReports(iStart,fnDoneAll) {
iStart = (typeof iStart !== 'undefined') ? iStart : 0;
if ( iStart === 0 ) {
showBusyUI();
}
for(i=iStart; i<10; i++) {
if ( shouldPrintReport(i) ) {
var ps = { header: "Report: " + i };
printReport(ps).finally(function() {
postPrinting1();
postPrinting2();
printReports(i+1,fnDoneAll);
});
return;
}
}
// code relying on synchronous behaviour here.
// this code only executes if the loop reached completion
HideBusyUI();
fnDoneAll();
}
...
printReports(0,function() {
// code relying on synchronous behaviour here.
});
// code here will execute before prints complete.
See proof of concepts implementation
The above is unsupported but may provide a basis for a solution for maintaining fully synchronous behaviour at the javascript level but will block the browser with probably quite bad side-effects.