有时候同步调用和异步调用同时存在的时候,难免出现混乱。
来看个栗子:
function loadReady(callback){ var readyState = document.readyState; if(readyState ==='complete'||readyState==='interactive'){ typeof callback === 'function' && callback(); }else{ window.addEventListener('DOMContentLoaded', callback); } } loadReady(function(){ console.log('DOM已经加载解析完成了!'); }); console.log('准备开始....');
结果:
这不是我们想要的,我们希望是先打印“准备开始....”,可是事与愿违。
上面的执行结果是因为回调函数被提前执行了,换句话说,回调函数被当成同步函数执行了。
怎么修改一下执行流程呢?
可以这么修改:
function loadReady(callback){
var readyState = document.readyState;
if(readyState ==='complete'||readyState==='interactive'){
typeof callback === 'function' && setTimeout(callback, 0);
}else{
window.addEventListener('DOMContentLoaded', callback);
}
}
loadReady(function(){
console.log('DOM已经加载解析完成了!');
});
console.log('准备开始....');
修改之后的代码中,callback被当做setTimeout的回调函数执行了,setTimeout的第二个参数虽然是0,但是却是在任务消息队列中等待执行的,具体可以看《JS运行机制之 Event Loop 的思考》。
所以,有时为了避免混乱,适当把回调函数的同步调用转成异步调用是很有必要的,其实就不应该对异步回调函数进行同步调用。对于这个问题,可以看Effective JavaScript 详细介绍---
”
1、绝对不能对异步回调函数(即使在数据已经就绪)进行同步调用。
2、如果对异步回调函数进行同步调用的话,处理顺序可能会与预期不符,可能带来意料之外的后果。
3、对异步回调函数进行同步调用,还可能导致栈溢出或异常处理错乱等问题。
4、如果想在将来某时刻调用异步回调函数的话,可以使用 setTimeout 等异步API。”
看看promise的调用方式
为了避免同步调用和异步调用引起的混乱,promise规定promise只能使用异步的调用方式。
看看promise改写上面的代码:
function onReadyPromise() { return new Promise(function (resolve, reject) { var readyState = document.readyState; if (readyState === 'interactive' || readyState === 'complete') { resolve(); } else { window.addEventListener('DOMContentLoaded', resolve); } }); } onReadyPromise().then(function () { console.log('DOM已经加载解析完成了!'); //异步,执行了resolve()之后就异步的执行.then()里面的回调函数 }); console.log('准备开始....');