Javascript 脚本如何进行“等待/sleep()”? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a Javascript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
Javascript 权威指南第 5 版
Closure: The Definitive Guide
leavic
V2EX    Javascript

Javascript 脚本如何进行“等待/sleep()”?

  •  
  •   leavic 2015-11-10 14:41:40 +08:00 16108 次点击
    这是一个创建于 3630 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近在学 js ,拿几个网站写 grease m onkey 脚本练手,发现一个问题是有些网站的数据并不是直接在 html 中呈现的,而是通过 js 加载的,也许这就是所谓的 ajax 吧。

    现在问题是,如果我要用自己的脚本处理这部分数据,就必须保证这些数据被网站自己的脚本加载成功了才行,否则我的脚本是看不到这些数据的。

    因为 ajax 很多都是异步加载的,所以就算是把函数绑定到 windows. onload 方法上也不一定能保证脚本加载完了数据,现在的方法只能是用 setTimeOut 做个固定延时,但体验不太好,而且不是稳妥的做法。

    我希望的做法是用一个 while 循环检查页面内容是否加载成功,这个循环中,如果没有检测到内容,就让 js 脚本睡眠,类似 rtos 中线程主动释放 cpu 控制权一样。否则的话,这个循环就是个一直占用大量 CPU 资源的恶性循环,很容易让浏览器假死。

    不过找了一圈好像没有看到这样的函数,我总不能为了实现个 sleep ,自己在 Javascript 上写一套类 rtos 调度系统出来吧。我本来想用一堆10~100毫秒的setTimeOut来实现,结果发现这好像还是会让浏览器假死,似乎setTimeout并没有释放CPU资源。
    第 1 条附言    2015-11-10 15:29:35 +08:00
    我觉得我大概了解 setTimeOut 如何工作了,不知道对不对:

    setTimeOut 这就是个定时器,定时 X 毫秒后执行某个函数。

    但这个函数本身是非 block 的,也就是说我如果把它放在 while 循环中的话,理论上是可以无限执行的,执行这个函数本身并不消耗时间,它就是个一次性调度器。

    这样就可以解释我在 While 中调用 setTimeOut 为什么会占用大量 CPU 资源了,因为对 CPU 来说 setTimeOut 和其他语句是一样的,这依然是个 CPU 跑满的恶性循环。
    第 2 条附言    2015-11-10 15:33:05 +08:00
    参考我 14 楼的代码,相当于这个 while 循环里,脚本在一直 SetTimeOut ,一直定时执行某个函数,这 100 毫秒内 CPU 根本停不下来;
    而等到 100 毫秒的时候,实现 SetTimeOut 的函数就开始执行了,这里,嗯,估计有几万个函数在等待执行,不死才怪。
    第 3 条附言    2015-11-10 15:47:31 +08:00
    搞定了,用 setInterval 就行了:

    window. Onload= my_bug


    function my_bug() {
    console.log("OK!")
    task = setInterval(check_data, 100)
    function check_data() {
    links = document.getElementsByTagName("a")
    if (links.length >= 10) {
    clearInterval(task)
    for (var i = 0; i < links.length; i++) {
    orig_link = links[i].getAttribute('href')
    clean_link = orig_link.split("=")[3]
    console.log(clean_link)
    links[i].setAttribute('href', clean_link)
    }
    }
    else
    {
    console.log("Checking...")
    }
    }
    }
    39 条回复    2019-04-19 08:14:11 +08:00
    iamcho
        1
    iamcho  
       2015-11-10 14:50:17 +08:00
    .done()
    phithon
        2
    phithon  
       2015-11-10 14:51:52 +08:00
    学 Javascript ,心里必须有“回调函数”的概念。
    比如你进行的 ajax 操作,需要等待服务端返回包再处理数据。
    那你应该把处理数据的代码放在 ajax 的回调函数里,而不是像其他同步语言一样写在 ajax 代码后一行。
    leavic
        3
    leavic  
    OP
       2015-11-10 14:52:28 +08:00
    @iamcho 请问这是 jQuery 里的函数吗?有没有纯 js 的实现?
    leavic
        4
    leavic  
    OP
       2015-11-10 14:55:44 +08:00
    @phithon 我写的是 grease monkey 脚本,和网站服务器是独立的两个部分,网站的 ajax 操作,怎么能设置我的函数作为回调呢?
    BTW ,抛开回调的概念,难道就没有这种方法吗?因为交出 cpu 控制权做一个简单的 sleep ,这几乎在所有语言里都有,就算在 C 里也很容易实现。
    zzNucker
        5
    zzNucker  
       2015-11-10 15:01:25 +08:00
    @leavic JS 你还想让它交出 CPU 控制权,你想让浏览器 block 么
    ericls
        6
    ericls  
       2015-11-10 15:02:16 +08:00
    js 是 non-blocking 是单线程的,如果你 sleep 了 那全部都会 block 住。。。
    coolicer
        7
    coolicer  
       2015-11-10 15:03:49 +08:00
    @leavic 浏览器如果有这个 sleep 应该会把界面卡住吧,所以没发现可以这样。可以用一下 es6 的生成器,可以暂停 /重新 执行函数。
    ChefIsAwesome
        8
    ChefIsAwesome  
       2015-11-10 15:12:03 +08:00
    你光用 setTimeOut 怎么可能让浏览器假死
    frozen2013
        9
    frozen2013  
       2015-11-10 15:13:33 +08:00
    之前某墙攻击 github 的方法也是 setTimeout 来实现的,从没有人说那段循环加载导致引用那段 js 的网页死掉啊。 http://drops.wooyun.org/papers/5398
    恐怕你在 setTimeout 引的 function 里做了太多耗时的计算工作了,你可以用 settimeout 来循环检查是某资源否加载完毕,确定加载完毕后再去做其他耗时的计算工作。
    hronro
        10
    hronro  
       2015-11-10 15:16:25 +08:00
    xmhttp.Onreadystatechange= function(){

    }
    hronro
        11
    hronro  
       2015-11-10 15:19:01 +08:00   1
    我字还没打完怎么就发出去了
    XMLHttp.Onreadystatechange= function(){
    if(XMLHttp.readyState==4 && XMLHttp.status==200){
    //这里填完成数据加载后执行的内容
    }
    };
    Cloudee
        12
    Cloudee  
       2015-11-10 15:20:45 +08:00
    你是不是在 while 里面 setTimeout 了……你在函数的最后 setTimeout 这个函数自己。
    或者不用 while 循环用 setInterval
    hronro
        13
    hronro  
       2015-11-10 15:21:31 +08:00
    js 是单线程,但是可以是异步的。像 AJAX 就是典型的异步执行
    leavic
        14
    leavic  
    OP
       2015-11-10 15:23:09 +08:00
    @ChefIsAwesome

    while(True)
    {
    if(check_something()==False)
    {
    setTimeOut(nop_function,100)
    }
    }


    执行到这段明显卡住了,我希望的是 check_something 为 false 之后就 sleep 100 毫秒,这样占用的 CPU 时间顶多是有限次的 check_something()函数的执行时间,但实际看来并不是这样。
    leavic
        15
    leavic  
    OP
       2015-11-10 15:24:20 +08:00
    @frozen2013 我就是做了个很简单的 a=2,if(a==1) alert('something'),基本上只有一个赋值和一个判断命令会被使用,我就把这段话当 nop 用了。
    hronro
        16
    hronro  
       2015-11-10 15:24:46 +08:00
    @leavic JS 里应该没有 sleep 这样的东西
    breeswish
        17
    breeswish  
       2015-11-10 15:25:47 +08:00
    alert 会阻塞
    leavic
        18
    leavic  
    OP
       2015-11-10 15:26:04 +08:00
    @Cloudee 是的,我不是很清楚 setTimeOut 的工作方式,我以为这个函数可以实现非 block 的 sleep
    Cloudee
        19
    Cloudee  
       2015-11-10 15:29:14 +08:00   1
    @leavic 不是这样, setTimeout 是立即返回,并且在调度队列里面加上一个 n 毫秒后的任务,所以相当于你这个在不停地执行,并且不停地往调度队列里面添加新的任务。你的这个需求可以考虑用 setInterval ,并在函数体里面判断条件是不是满足,不满足就直接返回,满足再执行里面的逻辑。
    leavic
        20
    leavic  
    OP
       2015-11-10 15:30:44 +08:00
    @Cloudee 对,我刚刚想明白这点,这样可以解释为什么 while 会卡死了, setTimeOut 对 CPU 来说和其他语句是一样的。我确实该试试 setInterval ,谢谢!
    leavic
        21
    leavic  
    OP
       2015-11-10 15:33:39 +08:00
    @breeswish 不是这个问题,因为 alert 是绝对不会被执行的,问题出在 setTimeOut 是非阻塞的。
    nisnaker
        22
    nisnaker  
       2015-11-10 15:35:54 +08:00
    var t;
    t = setInterval(function(){
    if(check_something() == true) {
    clearInterval(t);
    // your funcs
    }
    }, 1000);


    大概这样。
    GtDzx
        23
    GtDzx  
       2015-11-10 15:38:15 +08:00
    @leavic 建议你花个一天时间读读《 Javascript The Definitive Guide 》,尤其是第 17 章 Handling Events 。浏览器端 JS 的事件驱动编程模型和一般的 C 语言程序有很大不同,我自己一开始也似懂非懂一知半解。与其针对一个具体例子分析来分析去,不如踏踏实实从头学习来的快。
    frozen2013
        24
    frozen2013  
       2015-11-10 15:43:39 +08:00
    额,你感到浏览器假死是因为 while ( true )生成了 n 多个 setTimeout,这个锅不能 setTimeout 来背.

    用 settimeout 做定时器要这么写
    function fn() {
    if (check_something()===false) {
    timer = setTimeout(fn, 100);
    }
    else {
    //TODO
    }
    }
    var timer = setTimeout(fn, 100);
    leavic
        25
    leavic  
    OP
       2015-11-10 15:46:22 +08:00
    @GtDzx 谢谢建议。

    其实我自己用 C 写过简单的 RTOS ,所以调度系统对我来说是没什么难度的,用 RTOS 的思想做支撑,这些东西其实很容易理解,有点像练武的人打通任督二脉:)

    我今天这个问题主要是没先检查 setTimeOut 函数是不是阻塞的,现在已经搞定了。
    leavic
        26
    leavic  
    OP
       2015-11-10 15:46:53 +08:00
    @frozen2013 对的,我改用 setInterval 就好了。
    Biwood
        27
    Biwood  
       2015-11-10 16:00:23 +08:00
    用 while 循环不就阻塞了整个 Javascript 线程么,本来就是单线程,你这么一搞别的 js 代码都无法执行了。用定时器也不是什么好办法,频率高的话一样很影响性能。应该会有一些比较 hack 的方法可以解决你的问题,但是我觉得这种操作太敏感了吧,直接捕捉别人网站的数据?
    pagxir
        28
    pagxir  
       2015-11-10 16:07:57 +08:00 via Android
    很明显,以楼主目前的水平,很难跟他解释清楚。
    leavic
        29
    leavic  
    OP
       2015-11-10 16:14:50 +08:00
    @Biwood 只是过滤掉我不想要的内容
    leavic
        30
    leavic  
    OP
       2015-11-10 16:16:13 +08:00
    @Biwood 我用定时器坐下来效果很好了,因为本身操作量就很小。
    xiumushenzuo
        31
    xiumushenzuo  
       2015-11-10 17:20:21 +08:00
    建议题主还是看一下 23 楼推荐的内容,这和 RTOS 并不同,并不是多线程多任务的问题。
    你现在的方式的确可以达到效果,但消耗了很多浏览器资源,而且不够优雅,并不是 js 这个语言通用的解决方案。
    leavic
        32
    leavic  
    OP
       2015-11-10 18:49:25 +08:00
    @xiumushenzuo 谢谢,我会去看的。
    FrankFang128
        33
    FrankFang128  
       2015-11-10 18:55:38 +08:00
    永远不要在 JS 实现 Sleep
    maplerecall
        34
    maplerecall  
       2015-11-10 21:06:23 +08:00
    在 js 中用循环和模拟 sleep 的方式判断是否加载完成是十分不对的, js 由于其特性与使用场景所需要的编程思想与 c 等非脚本语言是不太一样的。

    lz 题目中描述的需求实际上是不需要任何 setTimeout 和 setInvertal 的,所要做的只是让这些 ajax 请求加载完成后来主动执行需要的操作。 23 楼的内容应该会有不少帮助~
    maplerecall
        35
    maplerecall  
       2015-11-10 21:17:57 +08:00
    @maplerecall 啊抱歉没看清楚前提_(:3 」∠)_
    如果是 grease monkey 这种插入式脚本可以通过监听 DOMNodeInserted 事件来判断是否已处理完请求到数据
    不过我记得这个似乎可以在页面加载前就执行?也许可以直接复写浏览器的 XMLHttpRequest 来直接获取请求数据 XD
    leavic
        36
    leavic  
    OP
       2015-11-10 21:29:14 +08:00
    @maplerecall 但这还有个问题,例如京东页面上的无货提示,就是 js 加载出来的,但并不是一定会有无货提示出现(碰上全有货的话),这种情况下,是不是 DOMNodeInserted 事件就不一定发生了。
    zhuangzhuang1988
        37
    zhuangzhuang1988  
       2015-11-10 21:41:39 +08:00
    用下老赵的 winjs
    dqh3000
        38
    dqh3000  
       2015-11-10 22:09:06 +08:00
    其实我很想说等到 es6 的 yield 或者 es7 的 async 就好了……

    不过大家的回答好像都跟我想的不一样……

    @FrankFang128
    SuperMonster009
        39
    SuperMonster009  
       2019-04-19 08:14:11 +08:00 via Android
    mdn 提到可以用 promise
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2517 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 05:16 PVG 13:16 LAX 22:16 JFK 01:16
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86