一个关于回调的问题,着急求解啊,请一定帮忙
发布于 12 年前 作者 markshao 6659 次浏览 最后一次编辑是 8 年前

小弟希望通过调用http的get服务,把返回的response中的某一个link赋值给一个变量然后给后续代码使用。问题是在回调中赋值的结果,在主线程中无法获取。

var url_path;

http.get(options,function(res){
    res.on("data",function(chunk){
        var data_json = JSON.parse(chunk.toString());
        var links = data_json.links;
        for (var i = 0;i<links.length;i++){
            if (links[i].rel == "down"){
                url_path = links[i].href;
                console.log(1)
            }
        }
    });
});

};

var second = new Date().getSeconds();
while (new Date().getSeconds() < second+6);
console.log(url_path);
console.log(2)

上面的代码就是希望把某一个rel为down的连接赋值为url_path, 这个http的操作很快的。我在主线程里面尝试了sleep了6秒钟,但是打印出来的url_path还是为undefined。而且奇怪的事情是,我代码中的打印数字的次序也不是我期望的,竟然是先打印2,再打印1,这个是为什么呢。求解啊,求高人帮忙啊

19 回复

搞明白同步和异步就知道了

花括号个数好像不匹配诶…

明白同步和异步啊。问题是回调函数是可以获取到外层变量的引用么。能够对外层变量进行赋值啊。可以是为什么我主线程出来的结果还是没有赋值成功的。

@markshao 因为是异步的,所以不会等待呀。不等待的话,你回调里面的赋值还没执行呢,下面的代码就直接while了,肯定没内容呀。

@olddog 我在while里面不是等待了六秒钟嘛,而且在console上看出来的确是等待了的,差不多是有6秒钟的样子的。我现在的疑惑是回调的线程是不是是独立,类似操作系统的fork。

@markshao 你那个while把整个进程给阻塞了,异步根本没机会执行。。。

@leizongmin 那么我们如何来等待异步的执行呢,还是说异步的代码调用是不允许在回调里面对外部变量进行赋值的。

@markshao 肯定需要在异步回调执行后做个动作的。。。

@markshao 可以这样理解异步:某天你家来了个客人,你想弄杯茶给客人喝,需要做这几个事情:烧水、洗茶杯、泡茶。

阻塞型的做法是:先烧水,一直盯着直到水开了,然后去洗茶杯,然后再泡茶;

如果是异步的话,则是:烧水,反正水一时半会还没开,顺便把茶杯给洗了,如果还有时间的话,跟客人聊聊天,等水烧开了再泡茶。

你的代码中的这部分就相当于一直盯着那个水壶,等水开了想要泡茶,可是茶杯还没洗好,于是杯具了:

var second = new Date().getSeconds(); while (new Date().getSeconds() < second+6); console.log(url_path); console.log(2)

你上面的代码可写成这样:

var url_path;

// 这个是回调函数,等获取到url_path后要执行的程序
var callback = function () {
  console.log(url_path);
  console.log(2)
};

http.get(options,function(res){
  res.on("data",function(chunk){
    var data_json = JSON.parse(chunk.toString());
    var links = data_json.links;
    for (var i = 0;i<links.length;i++){
      if (links[i].rel == "down"){
        url_path = links[i].href;
        console.log(1);
        // 执行回调函数
        callback();
      }
    }
  });
});

@leizongmin 说的有理,不过LZ的问题还是不是出在这里!!
res.on(“data”)是每次有新数据来的时候调用,这样http get获取的不完整数据每次来的时候都执行了同样代码,最后LZ认为获取完整的数据其实是不完整的!

http.get()的回调中这么写:  
var data = "";  
res.on("data", function(chunk){  
    data += chunck;  
});  
res.on("end", function(){  
    var data_json = JSON.parse(data.toString());  
    //下面自己处理吧
});    

顺便说下,最好还是要监听下"error"事件哦~~

res.on("error", function(err){  
    throw err;  
});  

@leizongmin

我根据你的提示下了一个demo测试了一下,

var p = 1;

http.get(options,function(res){ console.log(res.statusCode); res.on(“data”,function(chunk){ console.log(3); }); res.on(“end”,function(){ console.log(4); p = 4; }); });

结果还是不对的,

"C:\Program Files\nodejs\node.exe" httptest.js

1 200 3 3 3 3 4

我现在怀疑是我的while循环导致主线程被block了,由于nodejs是单线程的,所以可能那个时候的回调也没有执行。

所以我可以理解为,回调函数其实是在nodejs的主线程里面运行的,只有IO是在单独的线程中运行的。我这样的代码结构,就导致了,回调函数的执行顺序永远在while语句的后面。

可见nodejs不太适合这样的写法。犹豫我是因为拿这个来做一个测试restful的测试框架,需要每次case执行之前去把一个变量赋值。虽然可以把case写道回调中,但是总是会导致代码的可读性变低。

看来nodejs的用途还是有限的。不过可以考虑某些地方用同步的IO来实现。这样业务的逻辑性就可以比较好的保留了。

是你没理解回调,不是nodejs的用途问题。

我表示看到你上面的代码时很诧异,忘记刚开始时自己是怎么转变过来的

你的理解是对的,就是这样,只有一个线程。其实每个异步的“任务”都会塞进一个task queue里,等待main thread pick it up。当前的函数 while(true)了,main thread已经阻塞,自然没有机会pick up task

http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#event-loops

@markshao 你这个执行结果没有诡异之处~
说你的主线程被while循环block了,可以这么理解,但是不恰当!
首先nodejs是单线程的,无所谓什么主线程
然后nodejs顺序执行,遇到IO操作就会交给回调处理,然后继续向下运行,你的while循环当然没有IO操作,因此肯定先执行了,又因为执行while循环的时候,nodejs是没有出现等待IO这样的空闲的,所以即使这个时候http get操作已经获取数据完毕,也是得不到处理的机会的,只有等到while循环结束!
看你的执行结果:
第一个1我不知道你是log的什么
第二个200是while循环执行完毕后,处理http get的回调时log的res.status(code),表示数据接收成功
接下的四个3是从接收的数据piping出来的过程,所以要在“data”事件里写data += chunk
piping完数据后,触发end事件,所以最后log的是4~
(有些地方可能我也表述不清,只是说下我的理解!)

@sunshine1988 lz说main thread的意思是main thread这个概念(在browser里就是ui thread)吧。而且nodejs其他的api的执行是否有起线程又不清楚。。。lz说main thread只是说相对于c++(.node 模块)代码来说,js的执行线程类似main thread吧。

要获取urlpath,还有一个方法,就是用Events,把要处理urlpath的语句放在事件里面,然后events.emitt(‘Event’);

回到顶部