NODEJS如何优雅的实现同步?

方小葱 发布于 2017/08/11 14:02
阅读 670
收藏 0

先描述一下场景:

  我需要用NODEJS实现一个"无驱网络打印服务"实现:客户端(浏览器)-(httppost)->NODEJS打印服务-(socket)->ESC/POS小票打印机;

 GITHUB上找到了chn-escpos(https://github.com/baka397/chn-escpos) 这个项目;发现这个项目依赖node-printer

 有两个问题 1,node-printer在win依赖Print Spooler服务 2,node-printer使用本地代码 要gyp调用VS编译

所以 我准备 node-printer的依赖去掉,直接使用 NODEJS socket往端口写数据的方式和打印机通信;

看了一下chn-escpos这个项目以及他提供的测试web服务,他的流程是这样的:1,创建一个http服务器;2,用户POST一个打印任务列表(其中可包含多个)到http服务器 3,解析 然后 for 循环调用打印机完成批量打印 4,返回打印结果告知用户打印状态;

这里出现了"同步" 问题:1 以上的 1,2,3,4步应该依次进行;2假设打印任务列表中有十个打印任务,可能出现的情况有一下几种:a,无法打印,比如尝试一次打印之后发现打印机离线或者缺纸等,这样下面的任务就可直接取消 b,打印机存在亦可打印,但可能由于网络等问题 造成部分打印任务不成功;

总结一下:1,nodejs的http模块只提供异步方法,net模块提供的socket也只有异步方法;如何同步这两个异步方法;2,如何同步十个打印任务,在其中一个任务失败通知其他任务取消;

    /**
     * 发送命令
     * @param  {Function} callback function(err,msg),当执行打印后,回调该函数,打印错误返回err信息
     */
    sendCmd:function(callback){
        var _this = this;
        node_printer.printDirect({
            data: _this._queue.toBuffer(),
            printer: _this.printer,
            type: "RAW",
            success: function() {
                callback.call(_this, null, 'Print successed');
                _this._queue.empty();
            },
            error: function(err) {
                callback.call(_this, null, 'Print failed');
            }
        });
    },
	

因为环境问题,我具体没有测试chn-escpos; node_printer.printDirect()这个方法应该是个异步方法(当然这是我的猜测,winspooler可能是任务列队进行的,所以这个方法也有可能同步返回));但是 我爸这个方法改成用socket.write()的时候,这个方法中error回调被执行的时候 前面的http的请求早就已经返回了,所以我的到的结果是,无论打印是否成功,都得到了success的结果!!!!

具体代码我贴一下:

/**
 * 测试请求
 */

var url = require("url");
var querystring = require("querystring");
var printer = require('./lib/printer.js');

/**
 * 设置打印状态
 * @param  {Function} callback 回调函数,当打印成功后执行该函数
 */
function setPrinterStatus(callback) {
    this.return_status = []; //返回状态
    this.return_list = []; //需返回的列表
    if (callback) {
        this.callback = callback;
    }
}
setPrinterStatus.prototype = {
    /**
     * 增加状态列表
     * @param  {object} data 增加数据
     * @return {number}      列表编号
     */
    addList: function(data) {
        if (data) {
            this.return_list.push(data);
        } else {
            this.return_list.push({});
        }
        return this.return_list.length - 1;
    },
    /**
     * 更新列表
     * @param  {number} number 列表编号
     * @param  {object} data   更新数据
     * @return {boolen}        是否已全部更新
     */
    updateList: function(number, data) {
        this.return_status.push(1);
        for (var i in data) {
            if (data.hasOwnProperty(i)) {
                this.return_list[number][i] = data[i];
            }
        }
        if (this.return_status.length === this.return_list.length) {
            return true;
        } else {
            return false;
        }
    },
    /**
     * 获取列表
     * @return {object} 列表数据
     */
    getList: function() {
        return this.return_list;
    },
    callEnd: function() {
        if (this.callback) {
            this.callback.call(this);
        }
    }
}

//http请求响应
var app = require('http').createServer(function(req, res) {
    var objectUrl = url.parse(req.url);
    var objectQuery = querystring.parse(objectUrl.query);
    //接受数据
    var chunk = '';
    req.on('data', function(data) {
        chunk += data;
    });
    //回传内容
    req.on('end', function() {
        var data = {
            status: 0
        }
        if (!objectQuery.action) {
            data.msg = '没有指定动作';
        } else if (chunk) {
            switch (objectQuery.action) {
                case 'print':
                    var print_data = JSON.parse(chunk);
                    //生成回调函数
                    var print_status = new setPrinterStatus(function() {
                        data = {
                            status: 1,
                            msg: this.getList()
                        }
                        res.setHeader('Content-Type', 'application/json');
                        res.end(JSON.stringify(data));
                    });
                    //执行批量打印
                    for (var i = 0; i < print_data.length; i++) {
                        var list_number = print_status.addList({
                            'id': print_data[i].id,
                            'group_id': print_data[i].group_id
                        });
                        //添加返回列表
                        new printer(print_data[i].printer, function(err, msg) {
                            //查找打印机出错
                            if (err) {
                                print_status.updateList(list_number, {
                                    status: 0,
                                    msg: msg + ':' + err.toString()
                                });
                                //查找打印机成功
                            } else {
                                //开始打印
                                this.compile(print_data[i].content).print(function(err, msg) {
                                    //打印失败
                                    if (err) {
                                        print_status.updateList(list_number, {
                                            status: 0,
                                            msg: msg + ':' + err.toString()
                                        });
                                        //打印成功
                                    } else {
                                        print_status.updateList(list_number, {
                                            status: 1,
                                            msg: msg
                                        });
                                    }
                                });
                            }
                        });
                    }
                    print_status.callEnd();
                    break;
                case 'cmd':
                    var print_data = JSON.parse(chunk);
                    //生成回调函数
                    var print_status = new setPrinterStatus(function() {
                        data = {
                            status: 1,
                            msg: this.getList()
                        }
                        res.setHeader('Content-Type', 'application/json');
                        res.end(JSON.stringify(data));
                    });
                    //执行批量打印
                    for (var i = 0; i < print_data.length; i++) {
                        var list_number = print_status.addList({
                            'id': print_data[i].id,
                            'group_id': print_data[i].group_id
                        });
                        //添加返回列表
                        new printer(print_data[i].printer, function(err, msg) {
                            //查找打印机出错
                            if (err) {
                                print_status.updateList(list_number, {
                                    status: 0,
                                    msg: msg + ':' + err.toString()
                                });
                                //查找打印机成功
                            } else {
                                //发送执行命令
                                this.compile(print_data[i].content).sendCmd(function(err, msg) {
                                    //打印失败
                                    if (err) {
                                        print_status.updateList(list_number, {
                                            status: 0,
                                            msg: msg + ':' + err.toString()
                                        });
                                        //打印成功
                                    } else {
                                        print_status.updateList(list_number, {
                                            status: 1,
                                            msg: msg
                                        });
                                    }
                                });
                            }
                        });
                    }
                    print_status.callEnd();
                    break;
                default:
                    data = {
                        status: 0,
                        msg: '错误的动作'
                    }
                    res.setHeader('Content-Type', 'application/json');
                    res.end(JSON.stringify(data));
            }
        } else {
            data.msg = '没有数据';
            res.setHeader('Content-Type', 'application/json');
            res.end(JSON.stringify(data));
        }
    });
});
app.listen(process.env.PORT || 2520, function() {
    console.log('打印测试程序已运行,请参考github说明测试打印');
});

红色的部分:print_status.callEnd(); 总是比if(err)部分先执行; 由于打印接口(我说的是我的socket.write())是异步的,如何同步for循环中的N个任务!

问一下各位大牛:1,有没有简单的办法将这个代码改成同步的(请不要用"用async,co,then.js..." 这种粗暴的回答,虽然这个代码不多,但改成那样依然还是有工作量的,2 我想这个代码的}}}}}}}}已经够多了,大量的回调嵌套会增加代码的复杂度,降低代码的可读性!!3,JS(特指NODE环境运行下的JS是一个完备的计算机语言吗?还是他只适合某些场景?如果是这样,那NWJS存在的意义是什么?桌面程序不需要多高的性能不是吗?4,}}}}}}}}}}}}}}的终极解决方案是啥?

其他语言诸如java多数情况下习惯性选择容易理解的同步接口,但也提供了"基于多线程的"异步解决方案,还提供了线程同步的方法;也就是说只要我们愿意,就能很快用多线程"模拟"类似NODEJS这种异步机制;如果稍微花点心思也能"实现"这种单线程事件轮询;而不同于多数语言的是,NODEJS只标榜异步怎么怎么牛掰,并未在语言层面上解决同步问题,这是不是意味着NODEJS是不完备的的呢?诸如上面这种需求 应该是一个很常见的问题,对于打印小票,我不需要有多牛叉的效率,使用NODE而不适用py或者java是因为他们的运行环境体积太大了!!!!而node的早期版本 我通过压缩甚至能做成两兆多的安装包!!!

		    var wait = true;
			client.on("error",function(){
				console.log('无法连接网络打印机: ' + _this.host + ':' + _this.port);
				if(callback){callback.call(_this, "NET_ERROR", 'Print failed');}
				 wait = false;
			});
			client.on('close', function(data){
			    console.log('打印机断开');
				wait = false;
			});
			client.connect(_this.port, _this.host, function() {
			    console.log('CONNECTED TO: ' + _this.host + ':' + _this.port);
			    // 建立连接后立即向服务器发送数据,服务器将收到这些数据 
			    client.write(_this._queue.toBuffer());
			    client.end();
				wait = false;
			});
			while(wait){
				//
		    }

你们知道我甚至尝试了用while(wait){}这种方式了,但是很遗憾,程序死循环了,这只证明了一点:NODEJS确实是单线程的!!!!!!

加载中
0
张亦俊
张亦俊

Promise

高级一点的await/async

你不适应是因为你把java那套吃透了,思维都已经同步化了,所以对异步的世界不了解。

张亦俊
张亦俊
回复 @方小葱 : 你去先把Promise学懂,如果你的代码还是}}}}}}}}}}}},那一定是你代码有问题。回调地狱是JS的老毛病了,解决办法也是成熟的。await/async都是语法糖而已,找个babel可以给你翻译成ie都认识的js
方小葱
方小葱
实际上我觉得就以上的场景而言,我觉得异步的方式真的没有任何优势,更多的是丑陋的}}}}}}}}}}}}}} 强行异步只会把问题复杂化 原本1,2,3,4串行化上下文依赖的问题被强制并了,就需要付出代价....我准备用最早的node版本跑,大概支持不了await/async
方小葱
方小葱
思维方式大概也是有问题吧,但我觉得我更多的是在错误的场景使用了node;实际上相比于习惯,我的思维倾于简单,某些场景下我觉得异步比同步简单我就会倾向于异步;我觉得同步异步是两种必然的需求,同步能解决绝大多数的需求,而异步在某些场景下能简化问题.
0
AutoPlus
AutoPlus

基本功太差,需要学习阻塞和非阻塞的概念

0
孤单的不同世界
孤单的不同世界
我以前写过一个demo, 类似于 var socket=await connect () 当有一新请求时,connect才返回值,函数继续向下执行,不过要先把http.createServer包装下,有源码
0
孤单的不同世界
孤单的不同世界
你还可以用递归解决这种回调问题
返回顶部
顶部