异步函数
异步 Vs 同步
异步
异步函数就是在处理耗时较长的任务时,能够同时处理别的任务,而不用等待耗时任务的完成之后才可以执行其他任务。
常见的异步任务,包括:
Http请求
获取用户摄像头或麦克风
要求用户选择文件
异步程序的实现需要满足如下的几个条件:
通过一个函数来启动异步任务
异步任务启动后,立马返回;这样任务就会和其他事件独立
在一种不影响主线程的情况下,执行函数
当异步任务最终完成后,并通知将操作结果
同步
同步函数就是依次执行任务,一个任务执行完之后,才会执行下一个任务。
在Javascript中异步函数,主要用在事件处理和回调函数。
事件处理器
事件处理器是异步编程的一种方式:事件处理的函数,并不是被立即执行的,而是在任何事件被触发的时候,被调用执行。如果事件处理器是异步的,则事件应该通知异步函数调用者,异步函数的执行结果。
当使用同步函数作为事件处理器的时候,会导致主线程的卡顿
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>同步函数处理作为事件处理器</title>
<style>
textarea {
display: block;
margin-top: 20px;
width: 200px;
}
</style>
</head>
<body>
<button id="xhr">发送网络请求</button>
<button id="reload">重置</button>
<textarea rows="10">在点击:发送网络请求按钮后,在这里输入内容试试看</textarea>
<div class="content"></div>
<script>
const content = document.querySelector(".content");
document.querySelector("#xhr").addEventListener("click", ()=>{
for(let i=0;i<300000;i++){
console.log(i ** i)
}
content.textContent = "成功获取数据"
});
document.querySelector('#reload').addEventListener("click", function(){
content.textContent = "";
document.querySelector("textarea").value = "在点击:发送网络请求按钮后,在这里输入内容试试看";
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>异步函数处理作为事件处理器</title>
<style>
textarea {
display: block;
margin-top: 20px;
width: 200px;
}
</style>
</head>
<body>
<button id="xhr">发送网络请求</button>
<button id="reload">重置</button>
<textarea rows="10">在点击:发送网络请求按钮后,在这里输入内容试试看</textarea>
<div class="content"></div>
<script>
const content = document.querySelector(".content");
document.querySelector("#xhr").addEventListener("click", ()=>{
content.textContent = "获取数据中..."
setTimeout(() => {
content.textContent = "成功获取数据";
}, 5);
});
document.querySelector('#reload').addEventListener("click", function(){
content.textContent = "";
document.querySelector("textarea").value = "在点击:发送网络请求按钮后,在这里输入内容试试看";
})
</script>
</body>
</html>
回调函数
回调函数就是将函数A作为参数,传入另一个函数B,并在特定的时候再调用A。
当在回调函数中,一个最终的结果由多个异步操作的结果决定时,会出现回调地狱
的现象。
代码可读性差,调试起来比较困难
function firstFunc(init, func){
console.log('第一步开始')
setTimeout(() => {
console.log("第一步完成")
const result = init + 1
func(result,thirdFunc)
},1000)
}
function secondFunc(init, func){
console.log('第二步开始')
setTimeout(() => {
const result = init + 2
console.log("第二步完成")
func(result)
},2000)
}
function thirdFunc(init){
console.log('第三步开始')
setTimeout(() => {
console.log("第三步完成")
console.log(init + 3)
},3000)
}
function mainFunc(){
firstFunc(0,secondFunc)
}
mainFunc()
生成器处理异步函数
当使用生成器处理异步函数时,可以有效解决回调地狱
问题。
function firstFunc(init){
console.log('开始第一步操作')
setTimeout(() => {
console.log("第一步完成了",init)
ite.next(init + 1)
}, 1000);
}
function secondFunc(init){
console.log('开始第二步操作', init)
setTimeout(() => {
console.log("第二步完成了",init)
ite.next(init + 2)
}, 2000);
}
function thirdFunc(init){
console.log('开始第三步操作', init)
setTimeout(() => {
console.log("第三步完成了",init)
console.log(init + 3)
}, 3000);
}
function* main(){
let x1 = yield firstFunc(0)
let x2 = yield secondFunc(x1)
yield thirdFunc(x2)
}
ite = main()
ite.next()
yield
表达式本身没有返回值,或者说总是返回undefined
。next方法
可以带一个参数,该参数就被当作是上一个yield表达式
的返回值。
由于next方法
的参数表示上一个yield表达式
的返回值,所以在第一次使用next方法是,传递参数是无效的。只有从第二次使用next方法,参数才是有效的
。
换句话讲就是next(参数)
中的参数,将上一个yield表达式
的返回值,设置为参数
。
当利用生成器来处理异步函数的时候,语法解构上比回调函数简洁了一些,但是在应对异常处理的时候,仍然比较繁琐,需要在每个异步函数中都维护一个异常处理的
Promise处理异步函数
概念
Promise对象表示异步操作最终的状态(完成/失败)以及其结果值:
允许将处理程序与异步操作的最终成功值或失败原因关联起来
异步方法不会立即返回最终值,而是返回一个promise对象。这样可以使得异步方法可以像同步方法一样返回值
每一个Promise对象同一时刻,只能是pending
,fulfilled
和rejected
状态中的一个:
promise对象创建后的,初始状态为
pending
promise对象代理的操作完成时,其状态为
fulfilled
promise对象代理的操作发送异常时,其状态为
rejected
promise对象一旦变成
fulfilled
或rejected
后,状态不会再发生改变!
Promise包裹异步函数
Promise构造函数的形参是一个具有两个形参的函数
(resolve, reject) => { 异步函数主体 }
resolve是告知异步函数执行完成的标识,其后面的参数就是本次异步函数执行成功的返回值
reject是告知异步函数执行异常的标识,其后面的参数就是本次异步函数执行异常的返回值
形参函数的函数体是异步函数的主体
异步函数主体是同步被执行的
let promise = new Promise(
(resolved, rejected) => {
// 异步函数的主体
console.log("开始执行异步函数")
let cond = 12
if (cond > 10) {
setTimeout(()=>{
// 执行完成,返回
console.log("异步函数执行完成")
resolved(cond)
}, 2000);
} else {
rejected(new Error("cond 不满足条件"))
}
}
);
console.log('---');
Promise 链
then方法
当promise对象创建后,我们需要知道其代理的异步函数的执行结果,此时就需要通过promise.then()
方法实现。
then((success)=>{}, (error)=>{})
方法中接收两个形参,分别是异步函数执行成功和失败的,回调函数。回调函数的形参
success
和error
是promise
对象异步函数主体中resolve(success)
和reject(error)
的实参处理成功或失败的回调函数,在
promise
状态改变时,立刻被执行如果处理成功的回调函数不是函数时,js内部会被替换为一个
恒等函数 (x) => x
,将兑换值向前传递如果处理失败的回调函数不是函数时,js内部会被替换为一个
抛出器函数 (x) => {throw x}
,将抛出的异常向前传递
then()
的返回值是另一个promise对象
,该对象始终处于待定状态,无论当前Promise对象的状态如何新的promise对象的行为取决于成功或失败的回调函数的处理结果
返回一个值:以该返回值作为其成功时的值
没有返回任何值:以
undefined
作为其成功时的值抛出错误:以抛出的错误作为其拒绝值
返回Promise对象时,以该Prmise的成功/失败值作为其成功/失败值
所以
**resolve和reject只在初始的Promise对象中**
用于表明对象状态的完成和返回值。
由于then方法返回promise对象,这样就可以链式的调用Promise对象的方法了
let promise = new Promise(
(resolved, rejected) => {
// 异步函数的主体
console.log("开始执行异步函数")
let cond = 12
if (cond > 10) {
setTimeout(()=>{
// 执行完成,返回
console.log("异步函数执行完成")
resolved(cond)
}, 2000);
} else {
rejected(new Error("cond 不满足条件"))
}
}
);
console.log('---')
let promisea = promise.then(
(success) => {
console.log('处理异步函数的正常返回值')
console.log(success)
},
(error) => {
console.log('处理异步函数的异常返回值')
console.log(error)
}
);
catch方法
在then
方法中,我们如果只想处理异常时,会书写成promise.then(undefined, errfunc)
,catch(errFunc)
方法就是此种方式的简写形式。
promise.catch(errFunc)
在promise
的状态变成异常时,立刻执行errFunc
函数其返回值为一个
新的Promise对象
异常捕获时的常见异常:
// 只有函数主体中的异常可以被捕获
// 异步函数中的异常不能被异步函数外面的捕获器捕获
const p1 = new Promise(
(resolve, reject) => {
setTimeout(() => {
throw new Error("异常发生了") // 此处的异常不会被捕获
}, 2000)
throw new Error("异常") // 此处的可以被捕获
}
);
p1.catch((err) => {
console.error(err)
})
finally方法
promise.finally(finallyFunc)
方法在promise
的状态被敲定时(成功/失败),被调用。
finallyFunc是一个无参的回调函数
返回值为一个新的
Promise
对象finallyFunc的返回值会被忽略,新的
Promise
对象的状态值依旧为其前面的promise的状态值finallyFunc中抛出错误,或返回拒绝的Promise对象时,新的Promise对象的状态就会变成异常
Promise方法
all方法
Promise.all()
静态函数接受一个Promise可迭代对象作为输入,并返回一个Promise
。当所有输入的Promise
都被兑现时,返回的Promise也将被兑现,并返回一个包含所有兑现值得数组。如果输入的任何Promise被拒绝,则返回的Promise将被拒绝,并带有第一个被拒绝的原因。
需要多个异步函数都被兑现时,再执行其他内容,可使用该静态方法。
是一个并发的方法
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve,100,'foo')
})
Promise.all([promise1, promise2, promise3]).then((value) => {
console.log(value); // [3, 42, 'foo']
})
allSettled方法
Promise.allSettled()
静态方法将一个Promise可迭代对象作为输入,并返回一个单独的Promise。当所有输入的Promise都被敲定是,返回的Promise将被兑现,并带有描述每个Promise结果的对象数组
是一个并发的方法
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) =>
setTimeout(reject, 100, 'foo'),
);
const promises = [promise1, promise2];
Promise.allSettled(promises).then((results) =>
results.forEach((result) => console.log(result.status)),
);
// Expected output:
// "fulfilled"
// "rejected"
any方法
Promise.any()
静态函数将一个Promise可迭代对象作为输入,并返回一个Promise
对象。当输入的任何一个Promise兑现时,这个返回的Promise将会兑现,并返回第一个兑现的值。当所有输入Promise都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的AggregateError拒绝。
任何一个Promise被兑现时,就会将Promise兑现。只有所有的Promise都被拒绝时,返回值才会被拒绝。
是一个并发的方法
const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));
const promises = [promise1, promise2, promise3];
Promise.any(promises).then((value) => console.log(value)); // quick
race方法
Promise.race()
静态函数将一个Promise可迭代对象作为输入,并返回一个Promise
对象。这个返回的promise会随着第一个promise的敲定而敲定。
是一个Promise并发方法
常用在响应超时的处理中。
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value);
// Both resolve, but promise2 is faster
}); // two
reject方法
Promise.reject()
静态方法返回一个已拒绝的Promise对象,拒绝原因为给定的参数。
function resolved(result) {
console.log('Resolved');
}
function rejected(result) {
console.error(result);
}
Promise.reject(new Error('fail')).then(resolved, rejected);
// Expected output: Error: fail
resolve方法
Promise.resolve()
静态方法以给定值解决一个Promise对象。如果该值本身就是一个Promise,那么该Promise将被返回;如果该值是一个thenable对象,Promise.resolve()
将调用其then()
方法及其两个回调函数;否则,返回的Promise将会以该值兑现。
const promise1 = Promise.resolve(123);
promise1.then((value) => {
console.log(value);
// Expected output: 123
});
const original = Promise.resolve(33);
const cast = Promise.resolve(original);
cast.then((value) => {
console.log(`值:${value}`);
});
console.log(`original === cast ? ${original === cast}`);
// 按顺序打印:
// original === cast ? true
// 值:33
async/await
async/await
在很大程度上是promise
的语法糖。
async
函数总是返回一个promise
在async
函数中使用await
关键字来暂停函数执行,等待一个promise
进入敲定状态。
执行到await
时,后面的代码就会整体被安排进一个新的微任务,此后的函数体变为异步执行。
async function foo(name) {
console.log(name, "start");
await console.log(name, "middle");
console.log(name, "end");
}
foo("First");
foo("Second");
// First start
// First middle
// Second start
// Second middle
// First end
// Second end