欢迎大家到访我的个人博客:suilfly.github.io/
异步编程
由于JS是单线程的,如果线程运行的时候发生阻塞,对用户来说是不好的体验,为了解决这个问题,就出现了异步编程:当发现一个需要较长时间执行的任务,就把这个任务交给一个模块去管理,完成后放入到任务队列中。当主线程执行完了自己的同步代码(依次按序执行的),就会不断的询问任务队列中是否有完成的任务
- 通过异步加载图片来体验任务操作
let img = new Image();//imgDOM
let img = new Image();//imgDOM
function loadImg(src,resolve,reject){
img.src = src;
img.onload = resolve;
img.onerror = reject;
}
loadImg("1.jpg",()=>{
console.log('图片加载完成');
},()=>{
console.log('图片加载失败');
});//由于需要通过路径来加载一个图片,需要一定时间来完成,所以异步
console.dir(img);
最后的结果
- 通过定时器来体验异步任务操作
//封装一个定时器
function interval(callback,delay){
let id = setInterval(()=>callback(id),delay)
}
interval((id)=>{
/* 实现div右移 */
let div = document.querySelector('div');
//Window.getComputedStyle()方法返回的style是一个实时的 CSSStyleDeclaration 对象,当元素的样式更改时,它会自动更新本身。
let left = Number.parseInt(getComputedStyle(div).left);
div.style.left = left+100+'px';
if(left > 300){
clearInterval(id);//停止移动
interval((id)=>{//让div 逐渐消失
let width = Number.parseInt(getComputedStyle(div).width);
div.style.width = width-10+'px';
if(width == 0)
clearInterval(id)
},100)
}
},600)
最后的结果
但是这样写后会发现,嵌套的定时器显得代码混乱,所以后来的Promise会使代码更加整洁,但是内部的原理不变
任务排列
顾名思义:任务队列,队列是一个先进先出的数据结构,所以任务调出的顺序取决于任务调入的顺序
- 通过文件加载来体验任务顺序
/* 1.js */
function first(){
console.log("first");
}
/* 2.js */
function second(){
first();
console.log("second");
}
//现在我要加载两个文件(加载文件需要异步操作),但是2.js依赖于1.js
function fileLoad(src,resolve = null){
let script = document.createElement('script');
script.src = src;
document.body.appendChild(script);
script.onload = resolve;
}
/* 希望1.js先加载完进入任务队列 */
fileLoad("1.js");
fileLoad("2.js",()=>{
second();
});
但是有时候也会出现2.js先加载的情况,导致
为了解决这个问题,我可以
function fileLoad(src,resolve = null){
let script = document.createElement('script');
script.src = src;
document.body.appendChild(script);
script.onload = resolve;
}
/* 先加载1.js 加载完成后执行onload事件 */
fileLoad("1.js",()=>{
/* 1.js onload事件:加载2.js */
fileLoad("2.js",()=>{
second();
});
});
结果
但是我们会看到,问题虽然解决了,但是出现了大量的嵌套,代码混乱,产生回调地狱,所以后来的Promise会使代码更加整洁
Promise 微任务
使用Promise执行的任务被放入微任务队列,setTimeout,setInterval的执行的任务放入的队列被称为宏任务队列,就优先级来说,微任务的优先级高于宏任务优先级
/* Promise的三种状态:待定(pending): 初始状态,既没有操作成功,也没有被拒绝。
已成功(fulfilled): 意味着操作成功完成。
已拒绝(rejected): 意味着操作失败。 */
let promise = new Promise((resolve,reject)=>{});
console.log(promise);//pending:准备状态,因为Promise构造函数中没有执行resolve或reject方法,所以是pending
let promise = new Promise((resolve,reject)=>{ resolve(); });
console.log(promise);//fulfilled,因为Promise构造函数中执行resolve方法
let promise = new Promise((resolve,reject)=>{ reject(); });
console.log(promise);//rejected,因为Promise构造函数中执行reject方法
Promise -链式调用
/* 执行顺序:Promise构造器内的代码是同步执行的,所以先执行 console.log('构造器内部');
再执行: console.log("console");
最后主线程的同步代码都执行完后,轮询微任务队列中的任务,执行then*/
let promise = new Promise((resolve,reject)=>{
reject('error');//当调用这个方法后,会把紧挨着的then的任务加入到任务队列中
console.log('构造器内部');
}).then((resolve)=>{
console.log('现在执行成功后的业务操作'+resolve);
},(reject)=>{ console.log('现在执行失败后的业务操作:'+reject) });
console.log("console");
链式调用
- Promise.then()返回的还是一个promise对象
let p1 = new Promise((resolve,reject)=>{
resolve();
})
let p2 = p1.then(a=>{ console.log('成功') },b=>{ console.log(b) })
/* 根据代码执行顺序的分析:p1 status为成功,微任务+1
p1: Promise {<fulfilled>: undefined}
p2: Promise {<pending>}*/
console.log(p1,p2);
/* 考虑下面 这种情况 */
setTimeout(()=>{
console.log(p1,p2);
})
/*
代码执行顺序:
1.p1的resolve,微任务+1(所以p1.then等待执行)
2.主线程中的同步代码:console.log(p1,p2);
3.定时器,宏任务+1
4.微任务队列和宏任务队列都有任务的情况下(且两者的任务没有依赖关系)轮询微任务队列:
执行p1.then=>输出:成功,微任务-1
5.执行宏任务:setTimeout中:console.log(p1,p2);此时的p1,p2的状态都发生更改:
都为=>Promise {<fulfilled>: undefined}
设置定时器后最终的输出:成功
28538(定时器编号)
Promise {<fulfilled>: undefined} Promise {<fulfilled>: undefined}
*/
当p1的状态为rejected时
let p1 = new Promise((resolve,reject)=>{
reject('失败');
})
let p2 = p1.then(a=>{ console.log('成功') },b=>{ console.log(b) })
/* p1:rejected p2:fulfilled */
- 链式调用总结:后一个then 就是对前一个Promise的处理
let p1 = new Promise((resolve,reject)=>{
resolve('成功');
}).then(a=>{
return '成功-任务-1';//返回值字符串的作用:给下一个微任务传参
}).then(a=>{
console.log(a);
})
/* 输出:成功-任务-1 */
let p1 = new Promise((resolve,reject)=>{
resolve('成功');
}).then(a=>{
return new Promise((resolve,reject)=>{});/* 返回一个新的promise,状态pending,所以第二个then等待这个promise状态的改变时才执行 */
}).then(a=>{
console.log(a);
})
/* 无输出 */
最后的输出顺序是:
微任务与宏任务
/*
执行顺序:1.宏任务队列中放入setTimeout
2.console.log('构造器内部');
3.console.log("console");
4.主线程执行完,
因为只有宏任务中有任务:4.1 往微任务中加入一个任务
4.2 console.log('setTimeout');
4.3 then中的resolve
*/
let promise = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('sucess');//微任务队列加入一个任务
console.log('setTimeout');
},0)
console.log('构造器内部');
}).then((resolveMsg)=>{
console.log('现在执行成功后的业务操作'+resolveMsg);
},(rejectMsg)=>{ console.log('现在执行失败后的业务操作:'+rejectMsg) });
console.log("console");
Promise 状态中转
let p1 = new Promise((resolve,reject)=>{
resolve('p1 成功');
}).then();
let p2 = new Promise((resolve,reject)=>{
resolve(p1);//注意这里传入的是一个Promise对象,那么这意味着什么呢?
}).then();
console.log(p1,p2);
/* 输出结果:Promise {<fulfilled>: "p1 成功"}
Promise {<fulfilled>: "p1 成功"}
*/
let p1 = new Promise((resolve,reject)=>{
reject('p1 失败');
})
let p2 = new Promise((resolve,reject)=>{
resolve(p1);//由于p1的状态是rejected,所以执行then 的第二个回调函数
}).then(msg=>{ console.log(msg); },rejectmsg=>{ console.log(rejectmsg+'error'); });
console.log(p1);
/*
p1:Promise {<rejected>: "p1 失败"} 由于p2执行了resolve所以最终p2的状态为fulfilled,
然后执行rejectmsg=>{ console.log(rejectmsg+'error');输出:p1 失败error
*/
/*这也就意味着,当p2中的resolve传入的值是Promise对象的时候,
就相当于传入了p1的状态,p2再根据p1的状态执行对应的回调函数*/