想品客老师的第十二天:异步和promise
js的异步编程写在这里
异步
举个例子:加载图片是个需要事件才能完成的事情,js位单线程,为了避免阻塞主线程(为了避免图片一直加载看不见后面的东西),可以使用异步操作
异步加载图片:
function loadImage(src, resolve, reject) {
let image = new Image()
image.src = src
image.onload = () => {
resolve(image)//将image传入resolve函数
}//加载成功触发onload
image.onerror = reject//失败触发reject
}
loadImage('./shushu.jpg', image => {
document.body.appendChild(image)
//console.log('图片加载完成')
}), () => {
console.log('加载失败')
}
resolve
和 reject
是回调函数,它们不会立即执行,而是等到图片加载成功或失败后才执行。这种机制允许你在图片加载的同时继续执行其他代码,而不需要等待图片加载完成。
onload
和 onerror
是事件监听器,它们会在特定事件发生时被触发。这种事件驱动的机制是异步编程的核心之一。
加载出黍黍的美貌
异步编程的特点
非阻塞:异步操作不会阻塞主线程,代码可以继续执行后续逻辑。
回调函数:异步操作完成后,通过回调函数通知结果(成功或失败)。
事件驱动:异步操作通常依赖于事件(如
onload
、onerror
)来触发回调。
定时器的任务轮询
定时器在定时结束后就将任务放进任务队列里
体验一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 100px;
height: 100px;
background: yellowgreen;
position: absolute;
}
</style>
</head>
<body>
<div></div>
<script>
function interval(callback, delay = 50) {
let id = setInterval(() => callback(id), delay);
}
interval(timeId => {
const div = document.querySelector("div");
let left = parseInt(window.getComputedStyle(div).left);
div.style.left = left + 10 + "px";
if (left >= 200) {
clearInterval(timeId);
interval(timeId => {
let width = parseInt(window.getComputedStyle(div).width);
div.style.width = width - 10 + "px";
if (width <= 20) {
clearInterval(timeId);
}
}, 100);
}
}, 50);
console.log("houdunren.com");
</script>
</body>
</html>
后面学了promise可以优化代码结构
通过文件依赖了解任务排序
回调达成异步的一个工具
文件有依赖关系的时候,执行的时候有可能出错
//html
function load(src, resolve) {
let script = document.createElement('script')
script.src = src
script.onload = resolve
document.body.appendChild(script)
}
load('./0204model.js',()=>{
model()
})
load('./0204model2.js',()=>{
model2()
})
//0204.model.js
function model() {
console.log('我是0204model的js')
}
//0204.model2.js
function model2() {
model()
console.log('我是0204model2的js')
}
为了解决依赖关系的报错,我们使用嵌套关系:
function load(src, resolve) {
let script = document.createElement('script')
script.src = src
script.onload = resolve
document.body.appendChild(script)
}
load('./0204model.js', () => {
load('./0204model2.js', () => {
model2()
})
})
这样就确保了文件的加载顺序了
后面学了promise可以优化嵌套
后盾人你讲的我真的看不懂
他在这里举了一个ajax的异步任务处理的栗子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>后盾人</title>
</head>
<body></body>
<script src="js/ajax.js"></script>
<script>
ajax(`http://localhost:8888/php/user.php?name=后盾人`, user => {
ajax(`http://localhost:8888/php/houdunren.php?id=${user.id}`, lessons => {
console.log(lessons);
});
});
console.log("houdunren.com");
</script>
</html>
我看不懂,我看弹幕里的人也看不懂,就这样吧
Promise微任务处理机制
Promise 是 JavaScript 中用于处理异步操作的对象,代表一个可能现在、将来或永远不会完成的操作及其结果。它有三种状态:
-
Pending(进行中):初始状态,操作尚未完成。
-
Fulfilled(已成功):操作成功完成。
-
Rejected(已失败):操作失败。
当一个Promise对象被实例化以后,即表示 Promise 进入 pending 初始化状态,准备就绪,等待运行。
一旦 Promise 实例运行成功或者失败之后,实例状态就会变为 fulfilled 或者 rejected,此时状态就无法变更。
Promises/A+规范
js任务优先级
宏任务与微任务
在任务队列中,其实还分为宏任务队列(Task Queue)和微任务队列(Microtask Queue),对应的里面存放的就是宏任务和微任务。
为什么有宏任务队列和微任务队列?
队列是先进先出的,如果不区分,按照顺序执行,执行的前一个异步任务耗时很长情况从而影响后一个异步任务的执行,
比如队列先进入一个一小时的定时器,接着再进入一个请求接口函数,请求接口函数最早也需要一个小时后才会响应数据。影响实际效果
因此浏览器就会将异步任务分为宏任务和微任务
因此不同的异步任务会有不同的执行优先级
宏任务(Macro Task)
宏任务是指由浏览器或 JavaScript 引擎提供的较大粒度的异步任务。常见的宏任务包括:
-
setTimeout
和setInterval
的回调 -
DOM 事件回调(如点击事件、滚动事件等)
-
requestAnimationFrame
-
I/O 操作(如文件读取、网络请求)
-
setImmediate
(Node.js 环境)
特点:
宏任务会被放入宏任务队列(Macro Task Queue)中。
每次事件循环(Event Loop)会从宏任务队列中取出一个任务执行。
宏任务的执行优先级较低,会在当前调用栈清空且微任务队列清空后执行。
微任务(Micro Task)
微任务是指更小粒度的异步任务,通常与 Promise 相关。常见的微任务包括:
-
Promise
的then
、catch
、finally
回调 -
MutationObserver
的回调 -
queueMicrotask
添加的任务
特点:
微任务会被放入微任务队列(Micro Task Queue)中。
微任务的执行优先级高于宏任务。
在当前调用栈清空后,事件循环会优先清空微任务队列中的所有任务,然后再执行宏任务。
事件循环中的执行顺序
JavaScript 的事件循环会按照以下顺序处理任务:
执行当前调用栈中的同步代码。
如果调用栈为空,检查微任务队列并依次执行所有微任务。
当微任务队列清空后,从宏任务队列中取出一个任务执行。
重复上述过程。
同步->微任务->宏任务->同步......
例如这段代码会怎么输出
setTimeout(() => {
console.log("setTimeout");
}, 0);
new Promise(resolve => {
resolve();
console.log("promise");
}).then(value => console.log("成功"));
console.log("后盾人");
先执行同步代码,创建promise本身是同步的,所以先创建promise,创建完立马执行他的执行函数,也就是resolve,所以第一个被打印的是【promise的立即执行函数里的同步部分】,也就是打印promise
第二个执行的是外部的同步任务,打印后盾人
第三步执行微任务,微任务队列里是promise里的then,打印为【成功】
第四步执行宏任务,也就是定时器,打印【setTimeout】
如果这么写,改变状态
setTimeout(() => {
console.log("setTimeout");
}, 0);
new Promise(resolve => {
//resolve();
console.log("promise");
}).then(value => console.log("成功"));
console.log("后盾人");
在这个代码中,因为 Promise
内部的同步代码已经执行完了,但由于没有调用 resolve()
,then
中的回调会一直等待 resolve()
的执行。因此,console.log("成功")
不会被执行。
宏任务的提升
对上面的代码做出改动:
new Promise(resolve => {
setTimeout(() => {
console.log("setTimeout");
resolve();//宏任务还没执行,所以这个微任务不能创建
}, 0);
console.log("promise");
}).then(value => console.log("成功"));
console.log("后盾人");
第一第二毋庸置疑就是同步任务,promise->后盾人
因为找不到微任务,所以执行宏任务,打印【setTimeout】
发现宏任务里还有微任务(这就相当于下一个循环,下一个从同步->微任务->宏任务的循环),执行立马的微任务,打印【成功】
即使调换顺序,也是先打印宏任务:
new Promise(resolve => {
setTimeout(() => {
resolve();//调换顺序以后也是先打印setTimeout
console.log("setTimeout");
}, 0);
console.log("promise");
}).then(value => console.log("成功"));
console.log("后盾人");
Promise单一状态与状态中转
如果我们修改上面的定时器时间为2s,那么立马的微任务也会延迟两秒执行
new Promise(resolve => {
setTimeout(() => {
resolve();//调换顺序以后也是先打印setTimeout
console.log("setTimeout");
}, 2000);
console.log("promise");
}).then(value => console.log("成功"));
console.log("后盾人");
更深入理解promise的工作原理
promise的状态的单向的,不可逆转,不可撤销的
let p1 = new Promise((resolve, reject) => {
reject('拒绝')//p1的状态为拒绝
resolve('成功')//后面的这个会被前面的覆盖
})
new Promise((resolve, reject) => {
resolve(p1)//把p1的状态传递给promise
//此处传过来的p1无论是不是promise类型,都会被转化为promise
}).then(
msg => {
console.log('msg:' + msg)
},//成功方法
error => {
console.log('error:' + error)
}//拒绝方法
)
切特鸡皮提你怎么回事?
Promise.then的基本语法
其实我觉得应该先讲语法在讲原理。。。
then的工作原理很简单,就是接收传递过来的数据是成功还是失败,成功执行then下面的第一个方法,如果失败执行第二个方法,即使只用到第二个方法,前面也是要随便填个参数的
如果有连续多个then,也会找到第一个有内容的then进行状态的对应操作
如果有连续两个处理的then,只执行第一个有内容的
promise.then本质上也是一个promise
区分状态的promise
会根据状态的改变改变,每个then是对上一个promise的处理,每个then返回的都是promise, 新生成的Promise状态 是跟上一个then里面回调函数的返回值息息相关的
链式操作前面返回来的一般都是个对象,不然操作不了
then的链式操作:
let p1 = new Promise((resolve, reject) => {
//reject('拒绝')
resolve('成功')
})
new Promise((resolve, reject) => {
resolve(p1)
}).then(
success=>{
return '我是第一层then返回来的值,我是success'
},
fail=>{
return '我是第一层then返回来的值,我是fail'
}
).then(
msg => {
console.log('msg:' + msg)
},
error => {
console.log('error:' + error)
}
)
当then返回一个pending的promise的时候:
let p1 = new Promise((resolve, reject) => {
//reject('拒绝')
resolve('成功')
})
new Promise((resolve, reject) => {
resolve(p1)
}).then(
success=>{
return new Promise((resolve,reject)=>{})//返回一个状态为
//pending(准备)的promise
//所以下面的then只能等待
},
fail=>{
return '我是第一层then返回来的值,我是fail'
}
).then(
msg => {
console.log('msg:' + msg)
},
error => {
console.log('error:' + error)
}
)
这时候就阻塞了,所以要在中间处理一下
听起来很复杂,其实就是一个promise有对应的处理方法
let p1 = new Promise((resolve, reject) => {
//reject('拒绝')
resolve('成功')
})
new Promise((resolve, reject) => {
resolve(p1)
}).then(
success=>{
return new Promise((resolve,reject)=>{ resolve('解决了') })//返回一个状态为fulfilled的promise
},
fail=>{
return '我是第一层then返回来的值,我是fail'
}
).then(
msg => {
console.log('msg:' + msg)
},
error => {
console.log('error:' + error)
}
)
妈呀 给我写傻了
let p1 = new Promise((resolve, reject) => {
// resolve("我是成功状态");
reject("我是拒绝状态");
})
.then(
value => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("我是最外面一圈的第一个then里面resolve方法返回的新Promise,我的状态为rejected");
}, 3000);
}).then(null, result => {
return new Promise((resolve, reject) => {
reject("我是最外面一圈的第一个then里面resolve方法返回的新Promise的then,我的状态为rejected");
});
});
},
reason => console.log("我是最外面一圈的第一个then的rejected方法的返回值:"+reason)
)
.then(
value => {
console.log("我是最外面一圈的第二个then的resolve的返回值:"+value);
},
reason => console.log("我是最外面一圈的第一个then的reject方法的返回值:"+reason)
);
这里有一个undefined,是因为第一个then没有显式返回一个值
当 then()
返回的 Promise
状态是 未决(即它在等待异步操作完成时),它 不会给下一个 .then()
返回值,因此下一个 .then()
会得到一个 undefined
作为它的成功回调的参数
作为修改让最外面的第一个then的rejected返回一个拒绝的promise
let p1 = new Promise((resolve, reject) => {
//resolve("我是成功状态");
reject("我是拒绝状态");
})
.then(
value => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("我是最外面一圈的第一个then里面resolve方法返回的新Promise,我的状态为fulfilled");
}, 3000);
}).then(null, result => {
return new Promise((resolve, reject) => {
reject("我是最外面一圈的第一个then里面resolve方法返回的新Promise的then,我的状态为rejected");
});
});
},
reason =>{
return new Promise((resolve, reject) => {
reject("我是最外面一圈的第一个then里面rejected方法返回的新Promise,我的状态为rejected");
});
}
)
.then(
value => {
console.log("我是最外面一圈的第二个then的resolve的返回值:"+value);
},
reason => console.log("我是最外面一圈的第二个then的reject方法的返回值:"+reason)
);
俄罗斯人写的代码吧,疯狂套娃