AJAX进阶

AJAX进阶

一、同步代码和异步代码

同步代码

逐行执行,需要原地等待结果后,才继续向下执行。

异步代码

调用后耗时,不阻塞代码继续执行,在将来完成后触发一个回调函数

const result = 0 + 1;
console.log(result);
setTimeout(() => {
    console.log(2);
}, 2000);
document.querySelector(".btn").addEventListener("click", () => {
    console.log(3);
});
console.log(4);

在这里插入图片描述
先输出1和4,再在设置的定时器倒计时完之前,点击多少次按钮,就输出多少次3,当倒计时结束,执行定时器内的代码(输出2)。

JavaScript中常见的异步代码有:

  1. setTimeout/setInterval
  2. 事件
  3. AJAX

【异步代码依靠回调函数接收结果】

二、回调地狱

先回顾一下回调函数:

当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函数。

例如:

定时器

setTimeout(function () {
    console.log("这是回调函数");
}, 3000);

这里的function () {console.log("这是回调函数");}就是回调函数。

AJAX

let xhr=new XMLHttpRequest();
xhr.onreadystatechange=function(){
    // 此函数整个过程会被调用4次
    if(xhr.readyState==4 && xhr.status==200){
        //把响应数据存储到变量result中
        let result=xhr.responseText;
        console.log(result);
    }
}
xhr.open("get","",true);
xhr.send();

这里xhr.onreadystatechange绑定的函数就是回调函数。

回调函数地狱

实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差异常捕获困难耦合性严重,后期不好维护。

例如:

setTimeout(function () {
    //第一层
    console.log("我");
    setTimeout(function () {
        //第二程
        console.log("们");
        setTimeout(function () {
            //第三层
            console.log("是");
            setTimeout(function () {
                // 第四层
                console.log("好");
                setTimeout(function () {
                    // 第五层
                    console.log("友");
                }, 1000);
            }, 1000);
        }, 1000);
    }, 1000);
}, 3000);

在这里插入图片描述

那要怎么解决这种问题呢?

1、Promise链式调用
  1. 依靠 then() 方法会返回一个新生成的 Promise 对象特性,继续串联下一环任务,直到结束。
  2. then() 回调函数中的返回值,会影响新生成的 Promise 对象最终状态和结果。

例子:

function fn(str) {
    let p = new Promise(function (resolve, reject) {
        //处理异步任务
        setTimeout(function () {
            resolve(str);
        });
    });
    return p;
}

fn("我们")
    .then((data) => {
    console.log(data);
    return fn("是");
})
    .then((data) => {
    console.log(data);
    return fn("好友");
})
    .then((data) => {
    console.log(data);
});

在这里插入图片描述

Promise链式调用也是容易造成代码冗余,不管什么操作都用then,一眼看过去全是then,这样也不利于代码维护的。

所以看下面的asyncawait

2、async和await使用

运用这两个关键字以一种更简洁的方式写出基于Promise的异步代码,无需刻意链式调用Promise

async

async关键字放在函数面前,可以表明这个函数是执行异步任务的,不阻塞代码往下面执行。

async function fn() {
    return "你好";
}
console.log(fn());

在这里插入图片描述

可以看到,async函数返回值是一个Promise对象。

既然如此,那应该也可以像Promise对象一样,按照成功和失败来返回不同的数据,处理成功时用then方法来接收,失败时用catch方法来接收数据:

async function fn() {
    const flag = true;
    if (flag) {
        return "处理成功";
    } else {
        throw "处理失败";
    }
}
fn()
    .then((result) => {
    console.log(result);
})
    .catch((result) => {
    console.log(result);
});
console.log("先执行,表明async声明的函数是异步的");

在这里插入图片描述

把flag改为false:

在这里插入图片描述

await

  • await关键字只能在使用async定义的函数中使用
  • await后面可以直接跟一个 Promise实例对象(可以跟任何表达式,更多的是跟一个返回Promise对象的表达式)
  • await函数不能单独使用
  • await可以直接拿到Promise中resolve中的数据。
function fn(str) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            if (str) {
                resolve(str);
            } else {
                reject("处理失败");
            }
        });
    });
}
//封装一个执行上述异步任务的async函数
async function demo() {
    const res1 = await fn("我们"); //await直接拿到fn()返回的promise的数据,并且赋值给res
    const res2 = await fn("是");
    const res3 = await fn("好友");
    console.log(res1, res2, res3);
}
//执行函数
demo();

在这里插入图片描述

在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值。

当代码执行到async函数中的await时,代码就在此处等待不继续往下执行,直到await拿到Promise对象中resolve的数据,才继续往下执行,这样就保证了代码的执行顺序,使异步代码看起来更像同步代码。

错误捕获

如果我们在上面async demo函数中再加上一行:

const res4 = await fn("");

运行后会报错:

在这里插入图片描述

并且不会输出res1、res2、res3。

所以我们需要进行错误捕获,使用try-catch来进行。

async demo改成:

async function demo() {
    const res1 = await fn("我们");
    const res2 = await fn("是");
    const res3 = await fn("好友");
    let res4;
    try {
        res4 = await fn("");
    } catch (e) {
        res4 = e;
    }
    console.log(res1, res2, res3, res4);
}

在这里插入图片描述

思考:但是难道每个await都要使用try-catch包围吗?会不会显得代码更加冗余。

三、事件循环-EventLoop

事件循环

JavaScript中有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理时间以及执行队列中的子任务。

因为JS是单线程的,为了不让耗时代码阻塞其它代码执行,设计了事件循环模型

这一点在BOM操作-CSDN博客有提到过,同样在该文章中,有提到过JS执行机制:

  1. 逐行执行同步代码,遇到异步代码就交给宿主浏览器环境执行。
  2. 异步有了结果后,把回调函数放入任务队列排队
  3. 调用栈 空闲后,反复调用任务队列里的回调函数。

而事件循环就是将任务队列的任务调用到空闲的调用栈中执行【执行代码和收集异步任务】,注意这个过程是反复执行的。

在这里插入图片描述

宏任务与微任务

异步任务就是分为宏任务微任务

宏任务

浏览器环境执行的异步代码。

常见的有:

JS脚本执行事件(Script)、setTimeout/setInterval、AJAX请求完成事件、用户交互事件等

微任务

JS引擎环境执行的异步代码。

常见的有:

Promise.then()/catch()

Promise本身是同步的,但是then和catch回调函数是异步的。

微任务空闲后才会执行宏任务。

在这里插入图片描述

所以可以更新一下执行代码的顺序:

  1. 执行同步代码。
  2. 遇到宏任务就交给浏览器环境执行,遇到微任务就交给JS引擎环境执行。
  3. 异步有了结果后,把回调函数放入任务队列(宏、微)排队
  4. 调用栈空闲后,先清空微任务队列,然后再执行下一个宏任务,再重复以上步骤。
练习
// 目标:回答代码执行顺序
console.log(1)
setTimeout(() => {
    console.log(2)
    const p = new Promise(resolve => resolve(3))
    p.then(result => console.log(result))
}, 0)
const p = new Promise(resolve => {
    setTimeout(() => {
        console.log(4)
    }, 0)
    resolve(5)
})
p.then(result => console.log(result))
const p2 = new Promise(resolve => resolve(6))
p2.then(result => console.log(result))
console.log(7)

代码应该输出:1 7 5 6 2 3 4

在这里插入图片描述

四、Promise.all 静态方法

概念

合并多个Promise对象,等待所有同时成功完成(或某一个失败),做后续逻辑。

在这里插入图片描述

语法

const p = Promise.all([Promise对象,Promise对象...]);
p.then(result =>{
    // result结果是:[Promise对象成功结果,Promise对象成功结果...]
}).catch(e => {
    // 第一个返回失败的promise对象,抛出的异常
})
let p1 = new Promise((resolve, reject) => {
  resolve('成功了')
})

let p2 = new Promise((resolve, reject) => {
  resolve('success')
})

let p3 = Promise.reject('失败')

Promise.all([p1, p2]).then((result) => {
  console.log(result) // ['成功了', 'success']
}).catch((error) => {
  console.log(error)
})

Promise.all([p1,p3,p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error) // 失败了,打出 '失败'
})

该方法用于将多个Promise实例,包装成一个新的Promise实例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值