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 · 15773 次点击
    这是一个创建于 3334 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近在学 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   ·   实用小工具   ·   5807 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 02:40 · PVG 10:40 · LAX 18:40 · JFK 21:40
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.