JavaScript中的异步函数

发布于:2025-02-11 ⋅ 阅读:(33) ⋅ 点赞:(0)

异步函数

异步 Vs 同步

异步

异步函数就是在处理耗时较长的任务时,能够同时处理别的任务,而不用等待耗时任务的完成之后才可以执行其他任务。

常见的异步任务,包括:

  • Http请求

  • 获取用户摄像头或麦克风

  • 要求用户选择文件

异步程序的实现需要满足如下的几个条件:

  1. 通过一个函数来启动异步任务

  2. 异步任务启动后,立马返回;这样任务就会和其他事件独立

  3. 在一种不影响主线程的情况下,执行函数

  4. 当异步任务最终完成后,并通知将操作结果

同步

同步函数就是依次执行任务,一个任务执行完之后,才会执行下一个任务。

在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表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数就被当作是上一个yield表达式的返回值。

由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法是,传递参数是无效的。只有从第二次使用next方法,参数才是有效的

换句话讲就是next(参数)中的参数,将上一个yield表达式的返回值,设置为参数

当利用生成器来处理异步函数的时候,语法解构上比回调函数简洁了一些,但是在应对异常处理的时候,仍然比较繁琐,需要在每个异步函数中都维护一个异常处理的

Promise处理异步函数

概念

Promise对象表示异步操作最终的状态(完成/失败)以及其结果值:

  • 允许将处理程序与异步操作的最终成功值或失败原因关联起来

  • 异步方法不会立即返回最终值,而是返回一个promise对象。这样可以使得异步方法可以像同步方法一样返回值

每一个Promise对象同一时刻,只能是pendingfulfilledrejected状态中的一个:

  • promise对象创建后的,初始状态为pending

  • promise对象代理的操作完成时,其状态为fulfilled

  • promise对象代理的操作发送异常时,其状态为rejected

  • promise对象一旦变成fulfilledrejected后,状态不会再发生改变!

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)=>{})方法中接收两个形参,分别是异步函数执行成功和失败的,回调函数。

    • 回调函数的形参successerrorpromise对象异步函数主体中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