什么是异步
在JavaScript中,程序的执行是顺序的,是单线程的,即一次只能做一件事情
比如:页面在加载图片或进行大量运算时,用户将无法进行其它操作
这个时候控制权并没有在用户手上,整个浏览器就像冻结一样,这叫做阻塞
异步就是为了能够解决这类问题而出现的
异步实现
1. 异步callbacks(旧)
1.1 基本用法
异步callbacks就是函数,且该函数作为参数传递给其它在后台执行的函数;
当后台运行结束,就通过调用callbacks函数,通知你工作已经完成(捕获到事件)
如:addEventListener()第二个参数
当监听到click事件的时候,就会调用回调函数(callback)
btn.addEventListener('click', () => { alert('You clicked me!'); let pElem = document.createElement('p'); pElem.textContent = 'This is a newly-added paragraph.'; document.body.appendChild(pElem); });
1.2 回调地狱
下面这个例子来自MDN官方文档
“ 我们来谈谈订购披萨作为类比。为了使你的订单成功,你必须按顺序执行,不按顺序执行或上一步没完成就执行下一步是不会成功的:
- 选择配料。如果你是优柔寡断,这可能需要一段时间,如果你无法下定决心或者决定换咖喱,可能会失败。
- 下订单。返回比萨饼可能需要一段时间,如果餐厅没有烹饪所需的配料,可能会失败。
- 然后你收集你的披萨吃。如果你忘记了自己的钱包,那么这可能会失败,所以无法支付比萨饼的费用!“
chooseToppings(function(toppings) { placeOrder(toppings, function(order) { collectOrder(order, function(pizza) { eatPizza(pizza); }, failureCallback); }, failureCallback); }, failureCallback);
对于每个回调函数都需要有一个failureCallback(),使得代码十分冗余
2. Promises(新)
2.1 基本用法
使用fetch来获取事件
then用来包含要执行的回调操作,且每个回调都接收前一个成功操作的结果作为输入,可对传过来的结果继续进行操作
catch用于捕获所有then中的错误
fetch('products.json').then(function(response) { return response.json(); }).then(function(json) { products = json; initialize(); }).catch(function(err) { console.log('Fetch problem: ' + err.message); });
2.2 Promise术语
1.创建promise时,其所处状态为pending(待定)
2.当promise返回时,称为resolved(已解决)
2.1 一个成功resolved的promise称为fullfilled(实现)。它返回一个值,可以通过.then()块链接到promise链的末尾来访问该值。.then()块中的执行程序函数将包含promise的返回值。
2.2一个未能resolved的promise称为rejected(拒绝)。它返回一个原因(reason),一条说明为什么拒绝的错误消息。可以通过将.catch()块链接到promise链的末尾来访问此原因。
2.3 Promise fullfill/reject后运行一些最终代码
将.finally()方法链接到promise链的末尾即可
myPromise .then(response => { doSomething(response); }) .catch(e => { returnError(e); }) .finally(() => { runFinalCode(); });
3. async和await
3.1 基本用法
将async关键字放在函数声明前,使其称为async function(异步函数)
如下,hello将返回一个promise对象
async function hello() { return "Hello" }; hello();
由上一小节2.Promises可知,可以使用.then()块进行后续操作
hello().then((value) => console.log(value))
await关键字只在异步函数里面才起作用
重写2.Promises中代码
async function myFetch(){ let response = await fetch('products.json'); let products = await response.json; initialize(); } myFetch() .catch(err=>{ console.log('Fetch problem: ' + err.message); })
可以看到通过关键字可以除去.then()代码块,代码更加简单易懂
3.2 代码时如何工作的?
Javascript运行到await时将暂停,并允许其它代码在此期间执行,直到异步函数调用返回其结果
let response = await fetch('products.json');
解析器会在此行上暂停,直到当服务器返回的响应变得可用时。
此时fetch()返回的promise将会完成(fullfilled)并赋值给response变量
接着解析器便会移动到下一行代码
此时products才能获取到服务器响应返回的数据
let products = await response.json;
4. 超时和间隔
4.1 setTimeout()
在指定的时间后执行一段代码.
setTimeout前两个参数必填,最后一个参数选填
第一个参数为要超时执行的函数
第二参数为超时事件
第三个为传入函数中参数(可选)
则这段代码效果为: 在执行sayHi函数前,系统将停留2000毫秒,并且传递‘Mr. GoodLooking’给sayHi()函数
function sayHi(who) { alert('Hello Mr. Universe!'); } let myGreeting = setTimeout(sayHi, 2000, 'Mr. GoodLooking');
清除(取消)超时
clearTimeout(myGreeting);
4.2 setInterval()
以固定的时间间隔,重复运行一段代码.
第二个参数表示每隔1000毫秒执行一次函数displayTime()
function displayTime() { let date = new Date(); let time = date.toLocaleTimeString(); document.getElementById('demo').textContent = time; } const createClock = setInterval(displayTime, 1000);
清除(取消)间隔
const myInterval = setInterval(myFunction, 2000); clearInterval(myInterval);
4.3 requestAnimationFrame()
setInterval()的现代版本;在浏览器下一次重新绘制显示之前执行指定的代码块,从而允许动画在适当的帧率下运行,而不管它在什么环境中运行.
function draw() { // Drawing code goes here requestAnimationFrame(draw); } draw();
参考资料:
https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Asynchronous