【问题标题】:Why there is no sleep functionality in javascript when there is setTimeout and setInterval?为什么有 setTimeout 和 setInterval 时 javascript 中没有睡眠功能?
【发布时间】:2011-08-15 11:50:15
【问题描述】:

为什么在 javascript 中没有这样的函数为其继续设置超时、保存必要的状态(范围对象和执行点)、终止脚本并将控制权交还给浏览器? 超时到期后,浏览器将加载回执行上下文并继续执行脚本,我们将拥有一个真正的非浏览器阻塞睡眠功能,即使 JS 引擎是单线程的也能正常工作。

为什么javascript中仍然没有这样的功能?为什么还要把代码切片成函数,设置超时时间到下一步才能达到休眠效果?

【问题讨论】:

    标签: javascript browser language-design


    【解决方案1】:

    我认为“睡眠”是您不希望在浏览器中出现的内容。

    首先,您可能不清楚会发生什么以及浏览器在您实际睡眠时应该如何表现。

    • 整个脚本运行时是否处于休眠状态?通常它应该是因为你只有一个线程运行你的代码。那么如果在睡眠期间发生其他事件会发生什么?他们会阻塞,一旦执行继续,所有阻塞的事件都会触发。这将导致您可能想象的奇怪行为(例如,在实际单击后一段时间,可能是几秒钟内触发的鼠标单击事件)。或者这些事件必须被忽略,这将导致信息丢失。

    • 您的浏览器会发生什么情况?如果用户单击(例如关闭窗口)按钮,它会等待睡眠吗?我认为不会,但这实际上可能会再次调用 javascript 代码(卸载),由于程序执行处于休眠状态,因此无法调用该代码。

    再三考虑,睡眠是程序设计不佳的标志。其实一个程序/功能/你的名字有一定的任务,应该尽快完成。有时您必须等待结果(例如,您等待 XHR 完成)并且您希望同时继续执行程序。在这种情况下,您可以而且应该使用异步调用。这带来了两个好处:

    • 所有脚本的速度都提高了(不会因为睡眠而阻塞其他脚本)
    • 代码在它应该执行的时候执行,而不是在某个事件之前或之后执行(如果两个函数检查相同的条件,这可能会导致其他问题,例如死锁......)

    ... 这导致了另一个问题:想象一下两段或更多段代码会调用睡眠。如果他们试图同时睡觉,他们会妨碍自己,也许是不必要的。这在你想调试的时候会带来很多麻烦,甚至可能你很难确定哪个函数先休眠,因为你可能会以某种方式控制这种行为。

    我认为这是 Javascript 的优点之一,睡眠不存在。然而,多线程 javascripts 如何在浏览器中执行可能会很有趣;)

    【讨论】:

    • 要么我不明白你的观点,要么你不明白我的观点。 :) SetTimeout 设置要在指定时间后执行的函数。睡眠函数会告诉浏览器“在此处终止脚本并在指定时间后跳回下一条语句。”这将涉及保存堆栈跟踪和范围对象,但本质上与 setTimeout 函数相同。所以它不会让整个浏览器休眠。在睡眠期间,其他事件可以触发并且可以被处理。而且它不会是多线程的。
    • 嗯,我明白你的意思了,但据我所知,睡眠方法/函数经常会阻塞你的程序或至少当前线程的执行。
    • ...请考虑以下情况:如果脚本在您的方法休眠时运行,您在访问全局范围内的变量时可能会遇到麻烦,因为它们可能会在休眠时更改其值。这就是为什么我认为这不是一个正确的做法,因为它可能会导致竞争条件。
    【解决方案2】:

    javascript是为单进程单线程运行而设计的,浏览器也将UI渲染放在这个线程中,所以如果你sleep线程,那么像gif动画和元素事件这样的UI渲染也会被阻塞,浏览器会在"没有响应”状态。

    【讨论】:

      【解决方案3】:

      也许 setTimeout 和 yield 的组合可以满足您的需求?

      What's the yield keyword in JavaScript?

      您可以在让浏览器继续工作的同时保持本地函数范围。

      当然,目前仅在 Mozilla 中?

      【讨论】:

        【解决方案4】:

        因为 JavaScript 中的“sleep()”会冻结网络浏览器并使其无响应,从而导致潜在的可怕用户体验。

        【讨论】:

          【解决方案5】:

          您想要的是yieldDeferreds 的组合(例如来自jquery)。

          它有时被称为伪线程、轻线程或绿线程。你可以在 javascript > 1.7 中对它们做你想做的事。方法如下:

          您需要先包含此代码:

          $$ = function (generator) {
              var d = $.Deferred();
              var iter;
              var recall = function() {
                 try {var def = iter.send.apply(iter, arguments);} catch(e) {
                    if (e instanceof StopIteration) {d.resolve(); return;}
                    if (e instanceof ReturnValueException) {
                        d.resolve(e.retval); return
                    };
                    throw e;
                 };
                 $.when(def).then(recall);      // close the loop !
              };
              return function(arguments) {
                   iter = generator.apply(generator, arguments);
                   var def = iter.next();        // init iterator
                   $.when(def).then(recall);     // loop in all yields
                   return d.promise();           // return a deferred
              }
          }
          
          ReturnValueException = function (r) {this.retval = r; return this; };
          Return = function (retval) {throw new ReturnValueException(retval);};
          

          当然还要调用 jquery 代码来获取 $ JQuery 访问权限(用于 Deferreds)。

          然后你就可以为所有的 Sleep 函数定义一次:

          function Sleep(time) {
            var def = $.Deferred();
            setTimeout(function() {def.resolve();}, time);
            return def.promise();
          }
          

          并使用它(连同其他可能需要一些时间的功能):

          // Sample function that take 3 seconds to execute
          fakeAjaxCall = $$(function () {
             yield (Sleep(3000));
             Return("AJAX OK");
          });
          

          还有一个功能齐全的演示功能:

          function log(msg) {$('<div>'+msg+'</div>').appendTo($("#log")); }
          
          demoFunction = $$(function (arg1, arg2) {
             var args = [].splice.call(arguments,0);
             log("Launched, arguments: " + args.join(", "));
             log("before sleep for 3secs...");
             yield (Sleep(3000));
             log("after sleep for 3secs.");
          
             log("before call of fake AjaxCall...");
             ajaxAnswer = yield (fakeAjaxCall());
             log("after call of fake AjaxCall, answer:" + ajaxAnswer);
          
             // You cannot use return, You'll have to use this special return
             // function to return a value
             log("returning 'OK'.");
             Return("OK");
             log("should not see this.");
          });
          

          如您所见,语法有点不同:

          记住:

          • 任何应该具有这些特性的函数都应该包含在$$(myFunc)
          • $$ 将从您的函数中捕获任何产生的值,并仅在以下情况下恢复它 产值计算完毕。如果它不是延迟的,它将起作用 还。
          • 使用“Return”返回值。
          • 这仅适用于 Javascript 1.7(在较新的 firefox 版本中受支持)

          【讨论】:

            【解决方案6】:

            听起来您在这里寻找的是一种以看起来同步的方式编写异步代码的方法。好吧,通过在新的 ECMAscript 7 标准(即将推出的 JavaScript 版本)中使用 Promisesasynchronous functions,您实际上可以做到这一点:

            // First we define our "sleep" function...
            function sleep(milliseconds) {
              // Immediately return a promise that resolves after the
              // specified number of milliseconds.
              return new Promise(function(resolve, _) {
                setTimeout(resolve, milliseconds);
              });
            }
            
            // Now, we can use sleep inside functions declared as asynchronous
            // in a way that looks like a synchronous sleep.
            async function helloAfter(seconds) {
              console.log("Sleeping " + seconds + " seconds.");
              await sleep(seconds * 1000); // Note the use of await
              console.log("Hello, world!");
            }
            
            helloAfter(1);
            console.log("Script finished executing.");
            

            输出:

            Sleeping 1 seconds.
            Script finished executing.
            Hello, world!
            

            (Try in Babel)

            您可能已经从输出中注意到,这与sleep 在大多数语言中的工作方式不同。我们的 sleep 函数不会在休眠时间到期之前阻止执行,而是立即返回一个 Promise 对象,该对象在指定的秒数后解析

            我们的helloAfter 函数也被声明为async,这导致它的行为类似。 helloAfter 在调用它时立即返回一个 Promise,而不是阻塞直到它的主体完成执行。这就是“脚本执行完毕”的原因。在“Hello, world!”之前打印。

            helloAfter 声明为async 还允许在其中使用await 语法。这就是事情变得有趣的地方。 await sleep(seconds * 1000); 导致 helloAfter 函数在继续之前等待 sleep 返回的 Promise 解决。这实际上就是您所寻找的:在异步 helloAfter 函数的上下文中看似同步的睡眠。一旦睡眠解决,helloAfter 继续执行,打印“Hello, world!”然后解决它自己的 Promise。

            有关 async/await 的更多信息,请查看 ES7 的 draft of the async functions standard

            【讨论】:

            • 这是一个关于 ES7 的优秀而正确的答案,但如果它展示了如何在 ES5 中实现相同的效果,那将是一个更好的答案。 OP 的问题归结为“为了在今天的浏览器中使用await/async 工作流,我必须手动实现多少 ES7 运行时?”
            • @Quuxplusone 好吧,您可以尝试使用生成器函数来模拟它,但即使是那些也需要 ES6。如果你只是直接使用 Promises,那么你就会失去使之类似于同步函数的语法。如果你被 ES5 卡住了,最好的办法是使用像 Babel 这样的转译器从 ES7 编译到 ES5。
            猜你喜欢
            • 2020-10-27
            • 2021-04-12
            • 2010-11-11
            • 2013-02-06
            • 2013-08-17
            • 2021-12-27
            • 1970-01-01
            • 2011-09-22
            相关资源
            最近更新 更多