漫谈javascript同步&异步编程的N种方式 - ps:将html2canvas变成同步的解决方案

本文探讨了JavaScript中异步编程的基本概念及多种实现方式,包括setTimeout、动态创建script等,并详细介绍了如何将异步转化为同步,以解决特定场景下的需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

声明
本文不涉及如何优雅的设计和实现异步编程,只是从最基础的方面阐述异步、同步编程的方式。
如想优化实现,请移步:
(似乎不应该放在这里,可能你们看到这里就直接跳走了-_#,所以, 亲,别走,留下来~~
 
开篇
先看个例子:
console.log('1');
setTimeout(function(){
    console.log('first time');
}, 10);

console.log('2');

var flag = true;
setTimeout(function(){
    flag = false;
    console.log('second time');
}, 0);

var t_start = Number(new Date());
//延迟一秒才执行
while ( t_start + 1000 > Number(new Date()) ) {
    console.log(flag);
}
console.log('last ' + flag);

输出:

(忽略红色错误)从上面的输出可以看出,setTimeout的执行都被 挂起来了,等函数的其它部分都执行完毕后,才会开始执行;
所以,即使setTimeout在0ms后立即执行,最后也是在while循环等待了1s之后,才真正执行,即while是会阻塞setTimeout的;
还有,second time在first time之前输出,因为first time等待了10ms,如果将这个10ms改为0,那么这两个输出就会调换位置。
 
而造成上面这一切的根本原因就是:js的运行是 单线程的,当一个程序占着cpu狂奔的时候,其它人无法打断,只有等待。
 
接下来,说明下同步和异步:
由于单线程,按理说js的执行应该是从上至下顺序的,同步执行就是js原本的样子。
说到异步,可能创始人Brendan Eich也没有想过会有这种实现,随着web页面的丰富和发展,展现越来越慢,后人慢慢的创新了异步这种方式,如图:
 技术上的解释就是: 异步就是延时,延时就是在等待(by司徒)
最终目的就是:当一个耗时的任务需要执行时,应该将它异步分发出去,以便及时响应用户的请求。
 
那么将同步的函数转为异步的方式有哪些呢?下面就来一一说明:
1、setTimeout和setInterval
话不多说,请看开篇的例子。
setInterval跟setTimeout一样,运行时,被挂起,后续的函数不会等待interval中的事件执行完毕才开始,而是立马开始。
当然,这两个生成的异步都可以控制时间,可以自主控制多少秒后才会fire。
 
2、image.onload和image.onerror
代码:
var image = new Image();
console.log('1.image async start');
image.src = 'http://www.baidu.com/img/bdlogo.gif';
image.onload = function(){
    console.log('2.image loading');
};
console.log('3.load over');
//image.src = 'http://www.baidu.com/img/1.png';
image.onerror = function(){
    console.log('4.image error');
};
var t_start = Number(new Date());
//延迟一秒才执行
while ( t_start + 1000 > Number(new Date()) ) {
    console.log('while true');
}
console.log('5.error over');

输出:

(忽略错误输出),我们可以看出原本要第二步输出的image loading却在最后才能输出。
如果我们将image的src置为一个404url,那么onerror的输出同上。
说明这两个函数确实是挂起等待,且异步运行的,至于运行的时间,则依赖于图片加载的时间,所以时间是不可控的。
注:img.onerror不能用于IE6-8
3、 动态创建可执行的jsonp script
代码:
script = doc.createElement("script");
script.setAttribute("type", "text/javascript"); script.setAttribute("src", scriptUrl); window.document.body.appendChild(script);
window[callback_name] = function(a){
    //do something
};

动态script加载的js代码,因为这个过程是异步的,所以通过回调函数来执行也就变成了异步。

 
4、script.onreadystatechange和script.onload
5、xhr.onreadystatechange和self.postMessage
postMessage虽然很快,但只支持非常新的浏览器版本
以上4、5,都是司徒老师说的,你们回家自己验证,然后回来告诉我。
 
 
 接下来,我再描述一下js代码异步加载的几种方式,如下:
1、 XHR Eval
    用 XHR 下载一段可执行的js字符串,并 使用eval('(' + responseText + ')')执行。

    这种异步方式,依赖于ajax的异步,如果将它改成同步,那么他就是同步的。 

2、XHR Injection

    用 XHR 下载,在页面中动态创建一个 script 元素,并将 responseText 作为其 text 。

    这种方式和1在本质上是一致的。
3、Script in Iframe

    把脚本放在 HTML 中,使用 ifame 来下载它,iframe的内容加载是异步的,所以js代码的加载也是异步的。
4、Script DOM Element

    动态创建一个 script 元素,把 src 指向脚本URL。

    script代码是动态加载的。
5、Script Defer

    给 script 标添加 defer 属性。高版本浏览器才支持。
6、document.write Script Tag    

    利用 document.write 把 <script src=""> 添加到 HTML 中。

    document.write(unescape("%3Cscript src='url' type='text/javascript'%3E%3C/script%3E"));

 

有人说,上面讲了这么多,你到底想要说明什么,是不是深井冰啊。

正如,没有买卖,就没有杀害。

所以下面进入正题...

 

正题

需求来源是使用html2canvas(开源插件访问这里)这个插件,实现对网页的截图。

html2canvas的原理就是根据指定的父节点dom,在canvas里面重绘所有的dom子节点,

然后将canvas另存为base64的png图片,最后实现截图。

普通的dom节点,都可以直接画出来,但对于跨域的图片需要用代理来动态加载图片,其实也是解决跨域的一种方式。

 

这个插件当然是不错的,费时的它当然也是采取异步的方式来做的。

但实际上,我们平台需求和实现的恶心程度,直接导致如果使用异步的方式,会影响最后的呈现效果,

所以,我们的终极boss就是需要将这个lib改为同步的方式,然后就有了上面一大段技术调研。

 

如上,知道了异步编程的N种方式,那么要将异步改为同步,就很简单啦,无非就是将延时改为及时嘛。

上html2canvas的源码:

1、首先映入眼帘的是img.onload 和 img.onerror

在保证跨域截图没有404错误图片的情况下,我们可以将img.onerror直接去掉,消除这个异步影响。

对于img.onload,直接将其方法体拿出来,将异步变为同步,最后setImageLoadHandlers方法变为:

function setImageLoadHandlers(img, imageObj) {
    //img.onload = function() {
      if ( imageObj.timer !== undefined ) {
        // CORS succeeded
        window.clearTimeout( imageObj.timer );
      }

      images.numLoaded++;
      imageObj.succeeded = true;
      img.onerror = img.onload = null;
      start();
    //};
}

这一步大功告成。

但在配置proxy的前提下,执行起来,依然是异步的,一步步调试......了很久很久...

 

2、proxyGetImage方法

  // TODO modify proxy to serve images with CORS enabled, where available
  function proxyGetImage(url, img, imageObj){
    var callback_name,
    scriptUrl = options.proxy,
    script;

    link.href = url;
    url = link.href; // work around for pages with base href="" set - WARNING: this may change the url

    callback_name = 'html2canvas_' + (count++);
    imageObj.callbackname = callback_name;

    if (scriptUrl.indexOf("?") > -1) {
      scriptUrl += "&";
    } else {
      scriptUrl += "?";
    }
    scriptUrl += 'url=' + encodeURIComponent(url) + '&callback=' + callback_name;
    script = doc.createElement("script");

    window[callback_name] = function(a){
      if (a.substring(0,6) === "error:"){
        imageObj.succeeded = false;
        images.numLoaded++;
        images.numFailed++;
        start();
      } else {
        setImageLoadHandlers(img, imageObj);
        img.src = a;
      }
      window[callback_name] = undefined; // to work with IE<9  // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
      try {
        delete window[callback_name];  // for all browser that support this
      } catch(ex) {}
      script.parentNode.removeChild(script);
      script = null;
      delete imageObj.script;
      delete imageObj.callbackname;
    };

    script.setAttribute("type", "text/javascript");
    script.setAttribute("src", scriptUrl);
    imageObj.script = script;
    window.document.body.appendChild(script);

  }

在这个方法的最后,发现了动态创建script的踪迹,它通过引入scriptUrl得到的jsonp的可执行js代码,

找到window[callback_name]注册的回调方法,来异步的获取跨域图片,这个就是异步执行的另一个源头。

找到了源头,就找到了解决方法。

那就改呗。

为达到一样的效果,使用前述“js代码异步加载的几种方式”的第一种方法【1、XHR Eval】来加载需要回调执行的js代码,并保证它是同步的,

这时候,你要不要再回去看看这个方法?

不用!ok,修改后的代码如下:

$.ajax({
    url: scriptUrl,
    type: 'GET',
    success: function(res){
        eval('(' + res + ')');
    },
    async: false//同步
});

 

这样,万事大吉,马上能同步分享。

 

-------------------------------------------华丽丽的分割线-----------------------------------------------

如需查看效果,请前往:【百度众测-我的成长时间轴】,预计2014年2月20晚上8点正式上线,如不能访问,请在跳转的页面稍作停留,耐心等待。

 

如需转载,请注明来自:http://www.cnblogs.com/quenteenfix/

原文地址:http://www.cnblogs.com/quenteenfix/p/3556269.html

3ks...

 

 

 

 

转载于:https://www.cnblogs.com/quenteenfix/p/js-%e5%bc%82%e6%ad%a5-html%e6%88%aa%e5%9b%be-html2canvas.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值