javascript - 所有回调同时执行,但为什么呢?

标签 javascript node.js

我目前正在包装一个用 NodeJS 制作的可执行文件。可执行文件可以保存字符串以供可执行文件中的其他进程使用。每次可执行文件“保存”字符串时,它都会通过标准输出将指针发送回服务器。 NodeJS 服务器通过将字符串发送到可执行文件的 stdin 来保存字符串。

最初我是这样编写代码的:

CLRProcess.stdout.once('data',function(strptr){
    CLRProcess.stdout.once('data', function(str){
         console.log(str.toString())
    })
    CLRProcess.stdin.write("StringReturn " + strptr.toString())
})
CLRProcess.stdin.write("StringInject __CrLf__ Mary had a__CrLf__little lamb.")

上面的代码注入(inject)了一个字符串

Mary had a
little lamb.

接收指向字符串的指针,然后通过将指针发送回主机应用程序来在下一步中请求该字符串。

为了使编码算法更容易,我想要一个像这样的系统:

strPtr = Exec("StringInject __CrLf__ Mary had a__CrLf__little lamb.")
str = Exec("StringReturn " + strPtr)
// do stuff with str

这是我编写的代码:

class Pointer {
    constructor(){
        this.value = undefined
        this.type = "ptr"
    }
}


class CLR_Events extends Array {
    constructor(CLR){
        super()
        this.CLR = CLR
    }
    runAll(){
        if(this.length>0){
            //Contribution by le_m: https://stackoverflow.com/a/44447739/6302131. See Contrib#1
            this.shift().run(this.runAll.bind(this))
        }
    }
    new(cmd,args,ret){
        var requireRun = !(this.length>0)   //If events array is initially empty, a run is required
        var e = new CLR_Event(cmd,args,ret,this.CLR)
        this.push(e)
        if(requireRun){
            this.runAll()
        }
    }
}

class CLR_Event {
    constructor(cmd,args,ret,CLR){
        this.command = cmd;
        this.args = args
        this.CLR = CLR
        this.proc = CLR.CLRProcess;
        this.ptr = ret
    }

    run(callback){
        //Implementing event to execute callback after some other events have been created.
        if(this.command == "Finally"){
            this.args[0]()
            console.log("Running Finally")
            return callback(null)
        }

        //Implementation for all CLR events.
        var thisEvent = this
        this.proc.stdout.once('data',function(data){
            this.read()
            data = JSON.parse(data.toString())
            thisEvent.ptr.value = data
            callback(data);
        })
        this.proc.stdin.write(this.command + " " + this._getArgValues(this.args).join(" ") + "\n");
    }
    _getArgValues(args){
        var newArgs = []
        this.args.forEach(
            function(arg){
                if(arg.type=='ptr'){
                    if(typeof arg.value == "object"){
                        newArgs.push(JSON.stringify(arg.value))
                    } else {
                        newArgs.push(arg.value)
                    }
                } else if(typeof arg == "object"){
                    newArgs.push(JSON.stringify(arg))
                } else {
                    newArgs.push(arg)
                }
            }
        )
        return newArgs  
    }
}

var CLR = {}
CLR.CLRProcess = require('child_process').spawn('DynaCLR.exe')
CLR.CLRProcess.stdout.once('data',function(data){
    if(data!="Ready for input."){
        CLR.CLRProcess.kill()
        CLR = undefined
        throw new Error("Cannot create CLR process")
    } else {
        console.log('CLR is ready for input...')
    }
})
CLR.Events = new CLR_Events(CLR)

//UDFs

CLR.StringInject = function(str,CrLf="__CLR-CrLf__"){
    var ptr = new Pointer
    this.Events.new("StringInject",[CrLf,str.replace(/\n/g,CrLf)],ptr) //Note CLR.exe requires arguments to be the other way round -- easier command line passing
    return ptr
}
CLR.StringReturn = function(ptr){
    var sRet = new Pointer
    this.Events.new("StringReturn",[ptr],sRet)
    return sRet
}

CLR.Finally = function(callback){
    this.Events.new("Finally",[callback])
}

我的目的是执行以下操作:

  1. 函数 StringInjectStringReturnFinally 创建事件并将它们附加到 Events 数组中。
  2. Events 对象的 runAll() 函数从其数组中删除第一个“事件”并运行 run() 函数数组的,将其自身作为回调传递。
  3. run 函数写入可执行文件的 stdin,等待 stdout 中的响应,将数据附加到传入的指针,然后执行传递给它的 runAll() 函数。

这是我不明白的......执行多个字符串注入(inject)时:

S_ptr_1 = CLR.StringInject("Hello world!")
S_ptr_2 = CLR.StringInject("Hello world!__CLR-CrLf__My name is Sancarn!")
S_ptr_3 = CLR.StringInject("Mary had a little lamb;And it's name was Doug!",";")

我得到以下数据:

S_ptr_1 = {value:123,type:'ptr'}
S_ptr_2 = {value:123,type:'ptr'}
S_ptr_3 = {value:123,type:'ptr'}

数据应该在哪里:

S_ptr_1 = {value:1,type:'ptr'}
S_ptr_2 = {value:2,type:'ptr'}
S_ptr_3 = {value:3,type:'ptr'}

我认为会发生这种情况的唯一情况是,在伪代码中,发生以下情况:

CLRProcess.stdin.write("StringInject Val1")
CLRProcess.stdin.write("StringInject Val2")
CLRProcess.stdin.write("StringInject Val3")
CLRProcess.stdout.once('data') ==> S_ptr_1
CLRProcess.stdout.once('data') ==> S_ptr_2
CLRProcess.stdout.once('data') ==> S_ptr_3

但是为什么呢?我是否忽略了某些事情,或者该算法是否存在根本性错误?

最佳答案

我发现了问题并设法创建了解决方案。

问题

CLR.StringInject 被调用,它调用 CLR_Events.new() CLR_Events.new()首先检查数组是否为空或者是否有事件,创建一个新事件并将事件推送到 events 数组中。如果数组最初为空,则调用CLR_Events.runAll()

CLR_Events.runAll() 然后删除 CLR_Events.runAll() 数组的第一个元素并执行它,在 STDOUT 上设置监听器并将数据写入 STDIN .

然后运行下一行代码:

CLR.StringInject 被调用,它调用 CLR_Events.new() CLR_Events.new(),首先检查数组是否为空或者是否有事件,发现数组为空,所以调用runAll()

这就是问题所在。

runAll() 将调用自身来调用事件数组中的下一个事件。但是,Events 数组始终为空,因为 CLR_Events.new() 不知道如何检查当前是否正在执行事件。它只检查数组中是否有事件。现在我们已经向 STDIN 写入两次并在 STDOUT 上设置了 2 个监听器。这链接到第三个命令,最终意味着所有返回的对象都包含相同的数据。

<小时/>

解决方案

为了解决这个问题,我必须创建一个 this.isRunning 变量。

RunAll() 仅应在 isRunning == false 时调用

isRunning 仅当以下两个条件都为 true 时才应为 false:

  1. 当前没有正在执行的事件调用
  2. Events.length == 0

即回调后,isRunning 应设置为 false。

这可确保所有事件在同一回调循环内触发。

然后我的回调遇到了一些其他问题,因为 this 在以下位置中未定义:

function(){
    this.runAll.bind(this)()
    if (!(this.length>0))  this.isRunning = false
}

为了解决这个问题,我必须在回调定义之前添加一个 CLREvents 变量来存储 this,并将 this 替换为 回调中的 CLREvents。现在它终于按预期工作了。

完整的工作代码:

class Pointer {
    constructor(){
        this.value = undefined
        this.type = "ptr"
    }
}

class CLR_Events extends Array {
    constructor(CLR){
        super()
        this.CLR = CLR
        this.isRunning = false
    }
    runAll(){
        console.log('RunAll')
        this.isRunning = true
        if(this.length>0){
            //Contribution by le_m: https://stackoverflow.com/a/44447739/6302131. See Contrib#1
            var CLREvents = this
            this.shift().run(function(){
                CLREvents.runAll.bind(CLREvents)()
                if (!(CLREvents.length>0))  CLREvents.isRunning = false
            })
        }
    }
    new(cmd,args,ret){
        console.log("New Event: " + JSON.stringify([cmd,args,ret]) + " - requireRun:" + (!(this.length>0)).toString())
        //If events array is initially empty, a run is required
        var requireRun = !(this.length>0)

        var e = new CLR_Event(cmd,args,ret,this.CLR)
        this.push(e)
        if(!this.isRunning){
            this.runAll()
        }
    }
}

class CLR_Event {
    constructor(cmd,args,ret,CLR){
        this.command = cmd;
        this.args = args
        this.CLR = CLR
        this.proc = CLR.CLRProcess;
        this.ptr = ret
    }

    run(callback){
        console.log("RunOne")
        //Implementing event to execute callback after some other events have been created.
        if(this.command == "Finally"){
            this.args[0]()
            console.log("Running Finally")
            return callback(null)
        }

        //Implementation for all CLR events.
        var thisEvent = this
        this.proc.stdout.once('data',function(data){
            console.log('Callback')
            this.read()
            data = JSON.parse(data.toString())
            thisEvent.ptr.value = data
            callback(data);
        })
        this.proc.stdin.write(this.command + " " + this._getArgValues(this.args).join(" ") + "\n");
    }
    _getArgValues(args){
        var newArgs = []
        this.args.forEach(
            function(arg){
                if(arg.type=='ptr'){
                    if(typeof arg.value == "object"){
                        newArgs.push(JSON.stringify(arg.value))
                    } else {
                        newArgs.push(arg.value)
                    }
                } else if(typeof arg == "object"){
                    newArgs.push(JSON.stringify(arg))
                } else {
                    newArgs.push(arg)
                }
            }
        )
        return newArgs  
    }
}

var CLR = {}
CLR.CLRProcess = require('child_process').spawn('DynaCLR.exe')
CLR.CLRProcess.stdout.once('data',function(data){
    if(data!="Ready for input."){
        CLR.CLRProcess.kill()
        CLR = undefined
        throw new Error("Cannot create CLR process")
    } else {
        console.log('CLR is ready for input...\n')
        /* Example 1 - Using String Inject */
        S_ptr_1 = CLR.StringInject("Hello world!")
        S_ptr_2 = CLR.StringInject("Hello world!__CLR-CrLf__My name is Sancarn!")
        S_ptr_3 = CLR.StringInject("Mary had a little lamb;And it's name was Doug!",";")
        console.log(S_ptr_1)
        console.log(S_ptr_2)
        console.log(S_ptr_3)
    }
})
CLR.Events = new CLR_Events(CLR)

//UDFs

CLR.StringInject = function(str,CrLf="__CLR-CrLf__"){
    var ptr = new Pointer
    this.Events.new("StringInject",[CrLf,str.replace(/\n/g,CrLf)],ptr) //Note CLR.exe requires arguments to be the other way round -- easier command line passing
    return ptr
}
CLR.StringReturn = function(ptr){
    var sRet = new Pointer
    this.Events.new("StringReturn",[ptr],sRet)
    return sRet
}

CLR.Finally = function(callback){
    this.Events.new("Finally",[callback])
}

关于javascript - 所有回调同时执行,但为什么呢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44478230/

相关文章:

javascript - ngFor 中的下拉列表仅显示第一个列表项

javascript - 这个 Javascript 代码是什么意思?

node.js - "npm install -g n"错误

javascript - 如何在nodejs中的ejs文件中显示警报

javascript - 如何转义JS变量中的冒号?

javascript - angularjs 抛出错误 : [ng:areq] in resolving the lazyloading the scripts and adding a view controller separately

javascript - 如果处理为 'target',DOM 事件没有 'Object' 属性 - Atom 说

javascript - 使用 javascript 获取 UTC + 1 时间

JavaScript NodeJs - 如何重构这一点

node.js - NodeJS - Passport 登录时出现 404 错误