settimeout解决异步问题,settimeout无效

http://www.itjxue.com  2023-01-09 15:39  来源:未知  点击次数: 

setTimeout 丢帧

setTimeout通过设置一个间隔时间来不断的改变图像的位置,从而达到动画效果。但利用seTimeout实现的动画在某些低端机上会出现 卡顿、抖动 的现象。?

这种现象的产生有两个原因:

setTimeout的执行时间并不是确定的 。setTimeout任务被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此 setTimeout 的实际执行时间一般要比其设定的时间晚一些。

刷新频率受 屏幕分辨率 和 屏幕尺寸 的影响,不同设备的屏幕刷新频率可能会不同,而setTimeout只能 设置一个固定的时间间隔 ,这个时间不一定和屏幕的刷新时间相同。

以上两种情况都会导致setTimeout的执行步调和屏幕的刷新步调不一致,从而引起 丢帧 现象。?

为什么步调不一致就会引起丢帧呢?

setTimeout的执行只是在内存中对图像属性进行改变,这个变化必须要等到屏幕下次刷新时才会被更新到屏幕上。如果两者的步调不一致,就可能会导致中间某一帧的操作被跨越过去,而直接更新下一帧的图像。假设屏幕每隔16.7ms刷新一次,而setTimeout每隔10ms设置图像向左移动1px, 就会出现如下绘制过程:

第0ms:?屏幕未刷新,等待中,setTimeout也未执行,等待中;

第10ms:?屏幕未刷新,等待中,setTimeout开始执行并设置图像属性left=1px;

第16.7ms: 屏幕开始刷新,屏幕上的图像向左移动了 1px , setTimeout 未执行,继续等待中;

第20ms:?屏幕未刷新,等待中,setTimeout开始执行并设置left=2px;

第30ms:?屏幕未刷新,等待中,setTimeout开始执行并设置left=3px;

第33.4ms:屏幕开始刷新,屏幕上的图像向左移动了 3px , setTimeout未执行,继续等待中;

从上面的绘制过程中可以看出,屏幕没有更新left=2px的那一帧画面,图像直接从1px的位置跳到了3px的的位置,这就是丢帧现象,这种现象就会引起动画卡顿。

setTimeout()这个方法不执行

setTimeout()方法的第一个参数是一个闭包,它指定了要运行的函数。它的第二个参数是一个数值,表示的是以毫秒计算的运行延时。注意传递给setTimeout()的第一个参数也可以是表示执行代码的字符串,如果是字符串,那么setTimeout()方法会调用Function对象将这个字符串构造成函数执行。

setTimeout及setinterval都是异步执行的函数,就是它会在触发事件发生(延迟时间)之后去执行调用事件,这个过程并不会中断顺序执行的过程,而不是象c语言中的delay一样,延迟的是顺序执行的过程。而该过程大概是开启了另外的一个线程。

javascript中异步操作的异常怎么处理

一、JavaScript异步编程的两个核心难点

异步I/O、事件驱动使得单线程的JavaScript得以在不阻塞UI的情况下执行网络、文件访问功能,且使之在后端实现了较高的性能。然而异步风格也引来了一些麻烦,其中比较核心的问题是:

1、函数嵌套过深

JavaScript的异步调用基于回调函数,当多个异步事务多级依赖时,回调函数会形成多级的嵌套,代码变成

金字塔型结构。这不仅使得代码变难看难懂,更使得调试、重构的过程充满风险。

2、异常处理

回调嵌套不仅仅是使代码变得杂乱,也使得错误处理更复杂。这里主要讲讲异常处理。

二、异常处理

像很多时髦的语言一样,JavaScript 也允许抛出异常,随后再用一个try/catch

语句块捕获。如果抛出的异常未被捕获,大多数JavaScript环境都会提供一个有用的堆栈轨迹。举个例子,下面这段代码由于'{'为无效JSON

对象而抛出异常。

?

12345678

function JSONToObject(jsonStr) { return JSON.parse(jsonStr);}var obj = JSONToObject('{');//SyntaxError: Unexpected end of input//at Object.parse (native)//at JSONToObject (/AsyncJS/stackTrace.js:2:15)//at Object.anonymous (/AsyncJS/stackTrace.js:4:11)

堆栈轨迹不仅告诉我们哪里抛出了错误,而且说明了最初出错的地方:第4 行代码。遗憾的是,自顶向下地跟踪异步错误起源并不都这么直截了当。

异步编程中可能抛出错误的情况有两种:回调函数错误、异步函数错误。

1、回调函数错误

如果从异步回调中抛出错误,会发生什么事?让我们先来做个测试。

?

1234567

setTimeout(function A() { setTimeout(function B() { setTimeout(function C() { throw new Error('Something terrible has happened!'); }, 0); }, 0);}, 0);

上述应用的结果是一条极其简短的堆栈轨迹。

?

12

Error: Something terrible has happened!at Timer.C (/AsyncJS/nestedErrors.js:4:13)

等等,A 和B 发生了什么事?为什么它们没有出现在堆栈轨迹中?这是因为运行C 的时候,异步函数的上下文已经不存在了,A 和B 并不在内存堆栈里。这3

个函数都是从事件队列直接运行的。基于同样的理由,利用try/catch

语句块并不能捕获从异步回调中抛出的错误。另外回调函数中的return也失去了意义。

?

1234567

try { setTimeout(function() { throw new Error('Catch me if you can!'); }, 0);} catch (e) {console.error(e);}

看到这里的问题了吗?这里的try/catch 语句块只捕获setTimeout函数自身内部发生的那些错误。因为setTimeout

异步地运行其回调,所以即使延时设置为0,回调抛出的错误也会直接流向应用程序。

总的来说,取用异步回调的函数即使包装上try/catch 语句块,也只是无用之举。(特例是,该异步函数确实是在同步地做某些事且容易出错。例如,Node

的fs.watch(file,callback)就是这样一个函数,它在目标文件不存在时会抛出一个错误。)正因为此,Node.js

中的回调几乎总是接受一个错误作为其首个参数,这样就允许回调自己来决定如何处理这个错误。

2、异步函数错误

由于异步函数是立刻返回的,异步事务中发生的错误是无法通过try-catch来捕捉的,只能采用由调用方提供错误处理回调的方案来解决。

例如Node中常见的function (err, ...)

{...}回调函数,就是Node中处理错误的约定:即将错误作为回调函数的第一个实参返回。再比如HTML5中FileReader对象的onerror函数,会被用于处理异步读取文件过程中的错误。

举个例子,下面这个Node 应用尝试异步地读取一个文件,还负责记录下任何错误(如“文件不存在”)。

?

1234567

var fs = require('fs'); fs.readFile('fhgwgdz.txt', function(err, data) { if (err) { return console.error(err); }; console.log(data.toString('utf8'));});

客户端JavaScript 库的一致性要稍微差些,不过最常见的模式是,针对成败这两种情形各规定一个单独的回调。jQuery 的Ajax

方法就遵循了这个模式。

?

1234

$.get('/data', { success: successHandler, failure: failureHandler});

不管API 形态像什么,始终要记住的是,只能在回调内部处理源于回调的异步错误。

三、未捕获异常的处理

如果是从回调中抛出异常的,则由那个调用了回调的人负责捕获该异常。但如果异常从未被捕获,又会怎么样?这时,不同的JavaScript环境有着不同的游戏规则……

1. 在浏览器环境中

现代浏览器会在开发人员控制台显示那些未捕获的异常,接着返回事件队列。要想修改这种行为,可以给window.onerror

附加一个处理器。如果windows.onerror 处理器返回true,则能阻止浏览器的默认错误处理行为。

?

123

window.onerror = function(err) { return true; //彻底忽略所有错误};

在成品应用中, 会考虑某种JavaScript 错误处理服务, 譬如Errorception。Errorception

提供了一个现成的windows.onerror 处理器,它向应用服务器报告所有未捕获的异常,接着应用服务器发送消息通知我们。

2. 在Node.js 环境中

在Node 环境中,window.onerror 的类似物就是process 对象的uncaughtException 事件。正常情况下,Node

应用会因未捕获的异常而立即退出。但只要至少还有一个uncaughtException 事件处理

器,Node 应用就会直接返回事件队列。

?

123

process.on('uncaughtException', function(err) { console.error(err); //避免了关停的命运!});

但是,自Node 0.8.4 起,uncaughtException 事件就被废弃了。据其文档所言,对异常处理而言,uncaughtException

是一种非常粗暴的机制,请勿使用uncaughtException,而应使用Domain 对象。

Domain 对象又是什么?你可能会这样问。Domain 对象是事件化对象,它将throw 转化为'error'事件。下面是一个例子。

?

123456789

var myDomain = require('domain').create();myDomain.run(function() { setTimeout(function() { throw new Error('Listen to me!') }, 50);});myDomain.on('error', function(err) { console.log('Error ignored!');});

源于延时事件的throw 只是简单地触发了Domain 对象的错误处理器。

Error ignored!

很奇妙,是不是?Domain 对象让throw

语句生动了很多。不管在浏览器端还是服务器端,全局的异常处理器都应被视作最后一根救命稻草。请仅在调试时才使用它。

四、几种解决方案

下面对几种解决方案的讨论主要集中于上面提到的两个核心问题上,当然也会考虑其他方面的因素来评判其优缺点。

1、Async.js

首先是Node中非常著名的Async.js,这个库能够在Node中展露头角,恐怕也得归功于Node统一的错误处理约定。

而在前端,一开始并没有形成这么统一的约定,因此使用Async.js的话可能需要对现有的库进行封装。

Async.js的其实就是给回调函数的几种常见使用模式加了一层包装。比如我们需要三个前后依赖的异步操作,采用纯回调函数写法如下:

?

12345678910111213141516

asyncOpA(a, b, (err, result) = { if (err) { handleErrorA(err); } asyncOpB(c, result, (err, result) = { if (err) { handleErrorB(err); } asyncOpB(d, result, (err, result) = { if (err) { handlerErrorC(err); } finalOp(result); }); });});

如果我们采用async库来做:

?

12345678910111213141516171819202122

async.waterfall([ (cb) = { asyncOpA(a, b, (err, result) = { cb(err, c, result); }); }, (c, lastResult, cb) = { asyncOpB(c, lastResult, (err, result) = { cb(err, d, result); }) }, (d, lastResult, cb) = { asyncOpC(d, lastResult, (err, result) = { cb(err, result); }); }], (err, finalResult) = { if (err) { handlerError(err); } finalOp(finalResult);});

可以看到,回调函数由原来的横向发展转变为纵向发展,同时错误被统一传递到最后的处理函数中。

其原理是,将函数数组中的后一个函数包装后作为前一个函数的末参数cb传入,同时要求:

每一个函数都应当执行其cb参数;cb的第一个参数用来传递错误。我们可以自己写一个async.waterfall的实现:

?

12345678910111213141516171819202122

let async = { waterfall: (methods, finalCb = _emptyFunction) = { if (!_isArray(methods)) { return finalCb(new Error('First argument to waterfall must be an array of functions')); } if (!methods.length) { return finalCb(); } function wrap(n) { if (n === methods.length) { return finalCb; } return function (err, ...args) { if (err) { return finalCb(err); } methods[n](...args, wrap(n + 1)); } } wrap(0)(false); }};

Async.js还有series/parallel/whilst等多种流程控制方法,来实现常见的异步协作。

Async.js的问题:

在外在上依然没有摆脱回调函数,只是将其从横向发展变为纵向,还是需要程序员熟练异步回调风格。

错误处理上仍然没有利用上try-catch和throw,依赖于“回调函数的第一个参数用来传递错误”这样的一个约定。

2、Promise方案

ES6的Promise来源于Promise/A+。使用Promise来进行异步流程控制,有几个需要注意的问题,

把前面提到的功能用Promise来实现,需要先包装异步函数,使之能返回一个Promise:

?

12345678910

function toPromiseStyle(fn) { return (...args) = { return new Promise((resolve, reject) = { fn(...args, (err, result) = { if (err) reject(err); resolve(result); }) }); };}

这个函数可以把符合下述规则的异步函数转换为返回Promise的函数:

回调函数的第一个参数用于传递错误,第二个参数用于传递正常的结果。接着就可以进行操作了:

?

123456789101112131415

let [opA, opB, opC] = [asyncOpA, asyncOpB, asyncOpC].map((fn) = toPromiseStyle(fn)); opA(a, b) .then((res) = { return opB(c, res); }) .then((res) = { return opC(d, res); }) .then((res) = { return finalOp(res); }) .catch((err) = { handleError(err); });

通过Promise,原来明显的异步回调函数风格显得更像同步编程风格,我们只需要使用then方法将结果传递下去即可,同时return也有了相应的意义:

在每一个then的onFullfilled函数(以及onRejected)里的return,都会为下一个then的onFullfilled函数(以及onRejected)的参数设定好值。

如此一来,return、try-catch/throw都可以使用了,但catch是以方法的形式出现,还是不尽如人意。

3、Generator方案

ES6引入的Generator可以理解为可在运行中转移控制权给其他代码,并在需要的时候返回继续执行的函数。利用Generator可以实现协程的功能。

将Generator与Promise结合,可以进一步将异步代码转化为同步风格:

?

1234567891011

function* getResult() { let res, a, b, c, d; try { res = yield opA(a, b); res = yield opB(c, res); res = yield opC(d); return res; } catch (err) { return handleError(err); }}

然而我们还需要一个可以自动运行Generator的函数:

?

123456789101112131415161718192021222324252627282930

function spawn(genF, ...args) { return new Promise((resolve, reject) = { let gen = genF(...args); function next(fn) { try { let r = fn(); if (r.done) { resolve(r.value); } Promise.resolve(r.value) .then((v) = { next(() = { return gen.next(v); }); }).catch((err) = { next(() = { return gen.throw(err); }) }); } catch (err) { reject(err); } } next(() = { return gen.next(undefined); }); });}

用这个函数来调用Generator即可:

?

1234567

spawn(getResult) .then((res) = { finalOp(res); }) .catch((err) = { handleFinalOpError(err); });

可见try-catch和return实际上已经以其原本面貌回到了代码中,在代码形式上也已经看不到异步风格的痕迹。

类似的功能有co/task.js等库实现。

4、ES7的async/await

ES7中将会引入async function和await关键字,利用这个功能,我们可以轻松写出同步风格的代码,

同时依然可以利用原有的异步I/O机制。

采用async function,我们可以将之前的代码写成这样:

?

12345678910111213

async function getResult() { let res, a, b, c, d; try { res = await opA(a, b); res = await opB(c, res); res = await opC(d); return res; } catch (err) { return handleError(err); }} getResult();

和Generator Promise方案看起来没有太大区别,只是关键字换了换。

实际上async

function就是对Generator方案的一个官方认可,将之作为语言内置功能。

async function的缺点:

await只能在async function内部使用,因此一旦你写了几个async function,或者使用了依赖于async

function的库,那你很可能会需要更多的async function。

目前处于提案阶段的async

function还没有得到任何浏览器或Node.JS/io.js的支持。Babel转码器也需要打开实验选项,并且对于不支持Generator的浏览器来说,还需要引进一层厚厚的regenerator

runtime,想在前端生产环境得到应用还需要时间。

以上就是本文的全部内容,希望对大家的学习有所帮助。

关于JavaScript的异步settimeout里let变量的赋值问题

script

var i=1; //全局变量

function dd(i)

{

{

alert(i); //这里弹出的是全局变量i,所以是1

var i=0; //这里又定义了一个局部变量i,等于0,且每次执行都重新定义这个局部变量,并等于0;这个i的作用域范围就是在dd()函数体内,每次dd()函数执行完自动销毁

}setTimeout('dd(i)',1500)

}

setTimeout('dd(i)',1500)

/script

--------------------

要i等于0,在你的基础上有两个办法:

1:修改全局变量i

script

var i=1

function dd(i)

{

{

alert(i)

window.i=0 //这里修改的是第一行定义的那个i,第一次弹出1,以后弹出0

}setTimeout('dd(i)',1500)

}

setTimeout('dd(i)',1500)

/script

2:

script

var i=1

function dd(i)

{

{

var i=0

alert(i) //两句对调,但这个i就成了dd()函数体内的局部变量,所以一直弹出0

}setTimeout('dd(i)',1500)

}

setTimeout('dd(i)',1500)

/script

settimeout是异步的吗

setTimeout只运行一次,也就是说设定的时间到后就触发运行指定代码,运行完后即结束。如果运行的代码中再次运行同样的setTimeout命令,则可循环运行。

setinterval是循环运行的,即每到设定时间间隔就触发指定代码。这是真正的定时器。

setinterval使用简单,而setTimeout则比较灵活,可以随时退出循环,而且可以设置为按不固定的时间间隔来运行,比如第一次1秒,第二次2秒,第三次3秒……

如何理解setTimeout里面的异步

那个异步其实就是队列。

每行代码都是从上往下执行这你已经知道。

异步就是将代码添加到执行队列末尾。。

console.log(1);setTimeout(function(){ console.log(2);},0);//注意这里我写了间隔0秒console.log(3);

如果按照多线程的话,这样写有可能输出123也有可能输出132

但是js的单线程的,将异步代码放到了末尾执行,所以结果一定是132

(责任编辑:IT教学网)

更多

推荐通讯数据软件文章