最近工作中,需求方提出了一个打印条码的功能,需要将指定样本及其关联实验单的编号全部打印出来。
后端会把我需要的打码入参返回给我,前端需要做的是:引入厂家提供的js文件,调用提供的js方法初始化打印机,从后台获取打码参数,调用打码机打印方法,即可完成打码功能。
备注:涉及到打印的按钮共三种:打印当前样本条码,打印所有样本,打印所有样本并流转至待处理。
与后台沟通后,确定返回的打印参数是一个数组list,以便适应全部打印按钮。循环这个参数list,并逐个打印。
这其中遇到了几个值得记录的问题:
打码机厂家提供的初始化及打印方法放在一个立即执行方法中,无法直接引入调用;
由于初始化打码机、从后台获取参数均为异步方法,且为嵌套方法,那么打码机断开或打印失败时,怎样及时弹出错误提醒;
功能中包含一个全部打印并流转的按钮,如果打印失败,怎样阻止流转方法的调用。
获取打印机时,会把能识别的所有设备都拿到,有可能存在超过两个设备的情况(比如安装了多个打印设备的驱动等等),这时需要提供一个弹框,可以选择待使用的打印机。
2,3的主要难点在于打码机相关的方法均为原生xhr方法,怎样用Promise进行适当改写。
以下针对上面提到的四点一一记录。
1.模块方法改写
直接拿到的是一个min.js文件,核心方法如下:
.
..
var BrowserPrint=function(){
...
var e = {};
e.Device = function(a){
...
this.send = function(){...}
...
}
return e
}()
采用了立即执行函数来封闭内部私有方法,不会和全局函数互相污染,在原生js或JQuery中用得较多,其实这也是模块化的一种方式,但vue中主要采用import的方式引入文件,因此需要先将该方法导出。改写如下:
let BrowserPrint=function(){
...
let e = {};
e.Device = function(a){
...
this.send = function(){...}
...
}
return e
}
export default BrowserPrint
2.打印机获取失败提醒
这块出现场景主要是:断开打印机连接,点击打印按钮,无法正常打印时应弹出对应的错误提醒。
这个时候我们要调用的方法包括:获取打印机,再获取打码参数,打印条码。这三个方法均需要按序完成。
获取打印机用的是XMLHttpRequest 原生方法,使用回调函数会导致方法层层嵌套,不利于理解,考虑使用Promise进行改写。由于原方法偏复杂,在方法定义时直接改写难度较大,我选择在调用时封装进Promise中,并对应进行resolve和reject处理,避免回调地狱。
// 初始化打印机
initPrinter(){
printerData.printer = browserPrint();
return new Promise((resolve,reject)=>{
_BrowserPrint.getDefaultDevice("printer", (device)=>
{
...
_BrowserPrint.getLocalDevices((device_list)=>{
...
if(!Array.isArray(realDeviceList)||realDeviceList.length==0){
// 没有获取到打印机
typeof reject ==='function'&& reject()
}else{
typeof resolve ==='function'&& resolve()
}
}, function(error){
typeof reject ==='function'&& reject()
},"printer");
}, function(error){
typeof reject ==='function'&& reject()
})
})
}
this.initPrinter().then(res=>{}).catch(err=>{
utils.msg.error('获取打印机失败')
return 'error'
})
注意:定义了reject,调用时必须添加catch方法,否则会报错。
3.多层异步嵌套拿到最内层的结果,再进行相应处理
全部打印并流转是逻辑最复杂的方法,包括:
获取打印机(页面初始化后如果没有断开打印机可以省略这一步);
从后台获取打码参数;
逐一调用打印机进行打印;
打印成功则进行流转,打印失败则弹出错误提醒,并终止逻辑处理。
所有步骤按顺序进行。
其中,步骤1,2可以返回Promise实例,但步骤3仍然是一个xhr原生方法,所以决定在打印方法定义的地方(及下方的send方法)使用Promise进行改写。
// 原始方法
var m = this;
...
this.send = function(d, a, f) {
var g = b("POST", l + "write");
g && (void 0 !== m && (void 0 === a && (a = m.sendFinishedCallback), void 0 === f && (f = m.sendErrorCallback)), c(g, a, f), g.send(JSON.stringify({
device: {
name: this.name,
uid: this.uid,
connection: this.connection,
deviceType: this.deviceType,
version: this.version,
provider: this.provider,
manufacturer: this.manufacturer
},
data: d
})))
};
...
function c(a, b, d) {
a.onreadystatechange = function() {
a.readyState === XMLHttpRequest.DONE && 200 === a.status ? "" === a.responseType ? b(a.responseText) : b(a.response) : a.readyState === XMLHttpRequest.DONE && (d ? d(a.response) : console.log("error occurred with no errorCallback set."))
};
return a
}
// 修改后的方法
var m = this;
...
this.send = function(d, a, f) {
return new Promise((resolve,reject)=>{
var g = b("POST", l + "write");
g.onreadystatechange = function(){
if (g.readyState !== 4) {
return;
}
if (g.status === 200) {
// 正确回调
resolve("" === g.responseType ? g.responseText : g.response)
} else {
// 错误回调
reject(new Error(g.statusText));
}
}
g && (g.send(JSON.stringify({
device: {
name: this.name,
uid: this.uid,
connection: this.connection,
deviceType: this.deviceType,
version: this.version,
provider: this.provider,
manufacturer: this.manufacturer
},
data: d
})))
})
};
这样步骤3也可以返回Promise实例了。由于步骤1~3是不同打印诉求中都要用到,因此封装成一个公共方法_printTag。接下来遇到的一个问题就是,我怎样知道什么时候打印全部完成?
Promise.all方法仅能处理无依赖关系的多个异步方法。后面发现其实很简单,把嵌套内层的异步方法return出去就可以了。
类似这种结构:
test(){
this._test1().then(res=>{
return this._test2().then(res=>{
console.log('log1')
return Promise.resolve('success')
})
}).then(res=>{
console.log('log2')
if(res=='success'){
console.log('log3')
}
})
},
_test1(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('第一步')
})
})
},
_test2(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('第二步')
})
})
},
调用test方法时,我们希望打印出来的顺序是log1,log2,并且能打印出log3。通过测试发现,对test做以下改动即可:
改动1保证打印顺序是log1,log2,改动2保证可以拿到返回参数"success",可以执行打印log3。
4.多个打印设备
基本思路是:
获取打印机;
设备超过一台,出现选择设备弹框,停止接下来的打印操作;
弹框中点击“确定”按钮,继续开始打印操作。
其中,在步骤2出现过错误
// 出错代码示例
if(Array.isArray(printerData.deviceList)&&printerData.deviceList.length==1){
// 只有一台打印机
...
}else{
// 存在多个打印机,出现选择设备弹框
this.printDialog.visible = true;
return 'pause'
}
判断条件有一个漏洞,如果设备超过2个,那么一直会执行else逻辑。
正确的应该是:如果存在超过2个设备或者选择弹框已经出现,则执行打印操作。
// 调整后代码
if((Array.isArray(printerData.deviceList)&&printerData.deviceList.length==1)||this.printDialog.visible){
// 只有一台打印机或已出现选择弹框
...
}else{
// 存在多个打印机,出现选择设备弹框
this.printDialog.visible = true;
return 'pause'
}
总结:
这项功能的完成用了很多Promise改写,加深了对Promise使用方法的理解,比如必须使用resolve将Promise状态更改为“完成”,链式调用中才能用then;比如Promise调用时必须添加catch方法,否则可能会报错;比如then方法中return的值是下一个链式调用的返回值。这些问题以前都遇到过,但直接使用时经常不在意,出现这样或那样的问题。