【JavaScript基础】异步

异步

进程与线程

进程:拥有资源和独立运行的最小单位,是CPU资源分配的基本单位。进程之间相互独立,互不影响。

线程:建立在进程的基础上的程序运行单位,是CPU调度的最小单位。一个进程中可以有多个线程,同一进程中的线程可以互相通信,共享内存等资源,且可以并行执行。

        在同一时间内,系统可以运行多个进程。当我们启动一个程序时,CPU便会开启一个进程,并给这个进程分配内存资源。在一个进程中,可以有多个线程同时运行。

浏览器是多进程、多线程的

        浏览器是多进程的,每个Tab都是一个进程。进程间相互独立,防止某一标签页崩溃卡死而造影响整个浏览器。

浏览器多进程

  • Browser进程(主进程):浏览器进程,只有1个,负责主界面显示、处理用户交互、管理其他进程、管理网络资源...
  • Renderer进程:渲染进程,每个Tab(标签页)都有一个独立的进程,负责解析并渲染页面、处理JavaScript脚本等。渲染进程是多线程,包括GUI渲染线程、JavaScript引擎线程、事件触发线程、定时触发器线程、异步请求线程。
  • 第三方插件进程:插件进程,每种类型的插件对应一个进程,使用插件时创建
  • GPU进程:1个,处理应用GPU任务,图形处理、3D绘制...

渲染进程多线程

  • GUI渲染线程:渲染浏览器界面(解析HTML、CSS、DOM树构建、RenderObject树构建...)、重绘和回流。与JavaScript引擎线程互斥的,当JavaScript引擎执行时,GUI线程会被挂起,将GUI更新保存在队列中,等到JavaScript引擎空闲时立即被执行。
  • JavaScript引擎线程:处理JavaScript脚本。无论何时,浏览器都只有一个JS线程运行JS程序(主线程),即JavaScript语言的执行是单线程的
  • 事件触发线程:管理事件队列、监听事件。当事件被触发时,该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。
  • 定时触发器线程:计时。setIntervalsetTimeout在此线程中计时完毕后,把回调函数放入事件队列中,等待JavaScript引擎空闲时执行。
  • 异步请求线程:处理http请求。当发送异步Ajax请求时,浏览器会开启异步线程进行http请求,等待服务器响应,该线程监听到请求状态(XMLHttpRequest对象)变化,会产生状态变更事件,回调函数放入事件队列,等待JavaScript引擎执行。

JavaScript是单线程的

        JavaScript的运行环境(浏览器或Node.js 环境),只有一个主线程来处理所有的任务。所有的任务都需要排队;在同一时间,只能处理一个任务,等执行完后,才能执行下一个。

        但如果遇到执行时间长的任务,就会阻塞后续排队的任务执行。因此引入了异步编程模型,通过事件循环机制Event Loop)、回调函数、Promise对象/async/await语法实现异步编程,使程序可以在等待完成的同时,继续执行其他任务

同步任务和异步任务

js代码开始执行后,主线程执行栈会把任务分成两类:同步任务和异步任务

  • 同步任务:主线程上的任务,按顺序依次执⾏。
  • 异步任务:进入"任务队列"的任务。异步任务的执行不会阻塞调用栈中的同步任务。

任务队列:先进先出的数据结构,又称消息队列/事件队列。存放js单线程所遇到的异步任务,可以有多个。

        JavaScript会将异步任务交给宿主环境(浏览器或Node.js)来处理,待异步任务完成后,将其回调函数放入任务队列中并通知主线程,等待执行,等主线程上的任务执行完后,就会调取最早通知自己的回调函数,使其进入主线程中执行。

异步任务中的宏任务和微任务

        在ES6以前,JavaScrip未定义异步请求API,依赖于宿主环境(浏览器的XMLHttpRequest、Node.js的I/O 模块));任务队列仅有宏任务,不存在微任务机制。

        ES6新增Promise,实现微任务队列机制,JavaScript引擎可自主调度微任务,但具体的异步操作(如网络请求)仍需宿主环境提供API。

异步任务细分为宏任务、微任务

  • 宏任务(macro-task):被放到宏任务队列中等待执行;整体代码script、setTimeout / setInterval、Ajax请求、UI渲染、I/O操作、‌setImmediate(Node.js环境)...
  • 微任务(micro-task):被放到微任务队列中等待执行;Promise的then/catch/finally回调、await后面的代码、MutationObserver(HTML5,监听DOM树变化)、‌process.nextTick(Node.js环境)‌、queueMicrotask‌...

事件循环机制

事件循环执行机制(Event Loop)

  1. 执行首个宏任务script
  2. 立刻执行script中同步代码;遇到宏任务,放入到宏任务队列里;遇到微任务,放入到微任务队列里
  3. script中同步代码执行结束,先清空微任务队列
  4. 再取出下一个宏任务执行,执行该宏任务中的同步代码,将该宏任务中产生的宏任务和微任务放到对应队列
  5. 宏任务中同步代码执行完,先清空微任务队列
  6. 依次循环执行4、5,直到所有任务执行完毕

补充:微任务队列只有一个,宏任务队列可以有多个(多宏任务可能分优先级)

异步编程

Ajax

        Ajax:Asynchronous Javascript And XML(异步 JavaScript 和 XML)。通过XMLHttpRequest实现Ajax,也可以使用 fetch API 等现代方法。可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容。

发送Ajax请求

  1. 创建异步对象XMLHttpRequest
  2. 注册onreadystatechange事件,同时在事件处理函数增加校验readyState===4和status===200。当XMLHttpRequest状态变化时(服务端响应),调用onreadystatechange函数
  3. 使用open方法初始化请求:指定请求方法(如GET/POST)、目标URL、是否异步(默认为true,true异步/false同步)
  4. (可选)设置请求头:POST需要通过setRequestHeader设置Content-Type
  5. 使用send方法发送请求,GET方法传参null,POST传参数据

get 请求

const xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); // 1.创建XMLHttpRequest对象,ActiveXObject兼容IE5/IE6
xhr.onreadystatechange = function () { // 2.注册onreadystatechange事件,XMLHttpRequest状态改变时调用
    if (xhr.readyState === 4 && xhr.status === 200) { // 5.服务器响应,数据请求成功
        console.log('数据返回成功:' + JSON.stringify(xhr.responseText));
        // 使用XMLHttpRequest的responseText或responseXML属性获得来自服务器的响应
        // responseText获得字符串形式的响应数据
        // responseXML获得XML形式的响应数据
    }
};
xhr.open('get', 'api/data', true); // 3.初始化请求:设置参数
xhr.send(); // 4.发送请求

post请求

const xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); // 1.创建XMLHttpRequest对象
xhr.onreadystatechange = function () { // 2.注册onreadystatechange事件
    if (xhr.readyState === 4 && xhr.status === 200) { // 6.服务器响应,数据请求成功
        console.log('数据返回成功:' + JSON.stringify(xhr.responseText));
    }
};
xhr.open('post', 'api/data', true); // 3.初始化请求
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); // 4.设置请求头
xhr.send('username=lq&action=submit'); // 5.发送请求

封装Ajax请求

// 封装get请求Ajax
function getAjax(url, success, fail) {
    const xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            success && success(xml.responseText);
        }else{
            fail && fail(new Error('接口请求失败'));
        }
    };
    xml.open('get', url, true);
    xml.send();
}
//调用
getAjax('api/data', (res) => { console.log(res); });

// 封装 post请求Ajax
function postAjax(url, data, success, fail) {
    const xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            success && success(xml.responseText);
        } else {
            fail && fail(new Error('接口请求失败'));
        }
    };
    xhr.open('post', 'api/data', true);
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xml.send(data);
}
// 调用
postAjax('api/data', 'username=lq&action=submit', (res) => { console.log(res); });

JSON

JSON:一种轻量级的数据交换格式。用于前后端之间的数据传输,也被用于存储和交换数据

                <支持数字(整数或浮点数)、字符串(双引号包裹)、布尔值、数组、对象>

JSON示例

{
    "supportString": "JSON",
    "supportBoolean": true,
    "supportNumber": 2,
    "supportArray": [
        {
            "name1": "value1"
        },
        {
            "name2": "value2"
        }
    ],
    "supportObject": {
        "name": "value"
    }
}

JSON.stringify(Object数据):将JavaScript对象变成 JSON(序列化成一个 JSON 格式的字符串)

JSON.parse(json数据):将JSON字符串格式化为JavaScript对象

同源与跨域

同源策略:浏览器的一种安全策略,不允许非同源的URL之间进行资源的交互。

同源:指两个URL的域名、协议、端口都相同;只要有一项不同就是跨域

Promise

        为避免回调地狱(层层嵌套异步回调),可以更优雅地书写复杂的异步任务,ES6新增了Promise

        Promise:是一个处理异步操作的对象,有三种状态:pending(进行中)、fulfilled(已完成)、rejected(已失败)。

Promise实例的方法

        then(处理成功状态)、catch(处理失败状态)、finally(无论成功还是失败)

处理异步操作

        通过new Promise创建promise实例时,构造函数的参数(执行器函数)会被立即执行,此时promise状态初始化为pending;

        如果异步任务成功,调用resolve函数将promise状态变为fulfilled,此时会触发promise实例then方法的第一个参数(成功的回调函数),对成功的返回结果进行处理;

        如果异步任务失败,调用reject函数将promise状态变为rejected,此时会触发promise实例的then方法中的第二个参数(失败回调函数),对失败的返回结果进行处理,或promise实例的catch方法捕获错误;

        !状态从pending转换为fulfilled或rejected不可逆

// 通过构造函数Promise,创建一个promise实例对象
// Promise构造函数接受一个回调函数(被称为执行器函数)作为参数,在new Promise时被立即执行;promise状态被初始化为pending
// 执行器函数包含两个形参resolve和reject,用于将当前promise实例的状态从pending变为fulfilled或rejected

// 创建 promise 实例
const myPromise = new Promise((resolve, reject) => {
    // promise状态pending
    // 开始执行异步操作(例如ajax请求或开启定时器...)
    if ('异步操作成功') {
        // 如果异步任务成功,调用resolve函数,将promise状态变为fulfilled
        resolve('数据');
    } else {
        // 如果异步任务失败,调用reject函数,将promise状态变为rejected
        reject('错误信息');
    }
});


// 处理成功和失败,写法1
promise.then(result => {
    // 如果promise的状态为fulfilled,执行这里的代码
}).catch(error => {
    // 如果promise的状态为rejected,则执行这里的代码
});

// 处理成功和失败,写法2
// promise.then(
//     result => {
//         // promise的状态为fulfilled执行,处理promise的成功状态,result为resolve函数传过来的参数
//     },
//     error => {
//         // promise的状态为rejected执行,处理promise的成功状态,error为reject函数传过来的参数
//     }
// );

// 写法1和写法2稍有区别
// myPromise.then(onFulfilled).catch(onRejected):既可以捕获到myPromise的异常,也可以捕获到 then()里面的异常
// myPromise.then(onFulfilled, onRejected):只能捕获到myPromise的异常,无法捕获then()里面的异常
// myPromise的状态变为失败时,它会找到最近的那个失败回调函数并执行

then的链式调用

then()方法的返回值永远是一个 Promise 对象,所以可以对它进行链式调用

    const myPromise = new Promise((resolve, reject) => {
      resolve('success');
    });
    
    myPromise
      .then(res => {
          console.log('myPromise fulfilled', '--', res); // myPromise fulfilled -- success
          // 当then方法中的回调函数中,未设定返回值,默认为该then()返回:new Promise((resolve, reject) => { resolve(); }) ;
          // then()返回promise的状态为fulfilled
      })
      .then(res => {
          console.log('then 1 return promise fulfilled', '--', res); // then 1 return promise fulfilled -- undefined
          // 当then方法中的回调函数中,设置返回值是普通值/普通对象(没有then方法),该then()返回:new Promise((resolve, reject) => { resolve(设置返回值); }) ;
          // then()返回promise的状态为fulfilled
          return {info: 'objectInfo'};
      })
      .then(res => {
          console.log('then 2 return promise fulfilled', '--', res); // then 2 return promise fulfilled -- {info: 'objectInfo'}
          // 当then方法中的回调函数中,设置返回值为thenable对象(有then方法),会立即执行对象中then方法,并根据结果来决定then()方法返回的promise对象状态
          return { then: (resolve, reject) => { resolve('return a fulfilled thenable'); } };
      })
      .then(res => {
          console.log('then 3 return promise fulfilled', '--', res); // then 3 return promise fulfilled -- return a fulfilled thenable
          // 当then方法中的回调函数中,设置返回值是一个新的promise,then()返回promise的状态取决于新的promise
          return new Promise((resolve, reject) => { resolve('return 成功的 promise'); })
      })
      .then(res => {
          console.log('then 4 return promise fulfilled', '--', res); // then 4 return promise fulfilled -- return 成功的 promise
          return new Promise((resolve, reject) => { reject('return 失败的 promise'); })
      })
      .then(res => {
          console.log('then 5 return promise fulfilled', '--', res);
      }, err => {
          // 第5个then()返回promise的状态为rejected,执行失败的回调函数
          console.log('then 5 return promise rejected', '--', err); // then 5 return promise rejected -- return 失败的 promise
      })
      .then(res => {
          // 第6个then()返回promise的状态为fulfilled,执行成功的回调函数
          console.log('then 6 return promise fulfilled', '--', res); //  then 6 return promise fulfilled -- undefined
      }, err => {
          console.log('then 6 return promise rejected', '--', err);
      })
      // 补充:当then()方法传入的回调函数遇到异常或者手动抛出异常时,那么,then()所返回的新的promise会进入rejected状态

    catch的链式调用

    与then()方法类似,catch()方法也会返回一个新的Promise对象,可以对它进行链式调用

    const myPromise = new Promise((resolve, reject) => {
        reject('failed');
    });
    
    myPromise
        .catch(err => {
            console.log('myPromise rejected', '--', err); // myPromise rejected -- failed
            // 当catch方法中的回调函数中,未设定返回值,默认为该catch()返回:new Promise((resolve, reject) => { resolve(); }) ;catch()返回promise的状态为fulfilled
            // 当catch方法中的回调函数中,设置返回值是普通值,该catch()返回:new Promise((resolve, reject) => { resolve(设置返回值); }) ;catch()返回promise的状态为fulfilled
            // 当catch方法中的回调函数中,设置返回值为thenable对象(有then方法),会立即执行对象中then方法,并根据结果来决定catch()方法返回的promise对象状态
            // 当catch方法中的回调函数中,设置返回值是一个新的Promise,catch()返回promise的状态取决于新的promise
            // 当catch方法传入的回调函数,遇到异常或者手动抛出异常时,那么,catch()返回promise的状态处于rejected状态
            return 'catchSetReturnValue';
        })
        .then(res => {
            console.log('catch return promise fulfilled', '--', res); // catch return promise fulfilled -- catchSetReturnValue
        })
    

    补充:当promise抛出rejected异常时,一定要捕获并处理;如果不处理失败的回调,会报错;建议在末尾写一个 catch() 方法

    Promise类的方法

    • Promise实例的方法:需要先将Promise实例化,存放在Promise的prototype上
    • Promise类的方法:可以直接通过大写的Promise.xxx调用的方法,也称Promise的静态方法

    Promise 类的方法

    Promise.resolve()

    返回一个成功状态的Promise对象

    Promise.reject()返回一个失败状态的Promise对象
    Promise.resolve('promise fulfilled').then(res => console.log('Promise.resolve:', res)); // Promise.resolve: promise fulfilled
    Promise.reject('promise rejected').catch(err => console.log('Promise.reject:', err)); // Promise.reject: promise rejected

    Promise.race()

            返回跟与第一个执行完成Promise状态相同的Promise,并将结果传递给对应状态的回调函数

    Promise.any()

            当存在fulfilled状态的Promise时,返回一个成功的Promise,并将第一个执行为fulfilled状态Promise的结果传递给成功的回调;

            当所有Promise均为rejected时,传递给失败回调‌一个AggregateError(包含所有错误原因)

    Promsie.all()

            当所有Promise均为fulfilled时,返回一个成功的Promise,并向成功的回调传递:一个包含所有Promise结果的数组;

            只要有一个Promise为rejected,就返回一个失败的Promise,并向失败的回调传递:第一个执行为reject状态Promise的结果

    Proimse.allSettled()

            等待所有的Promise都有结果后,返回一个成功的Promise,并向成功的回调传递:一个包含所有Promise的结果的数组

    const promise1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('执行 promise1');
            reject('promise 1 失败');
        }, 1000);
    });
    const promise2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('执行 promise2');
            resolve('promise 2 成功');
        }, 2000);
    });
    const promise3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('执行 promise3');
            resolve('promise 3 成功');
        }, 3000);
    });
    
    
    Promise.race([promise1, promise2, promise3])
        .then((res) => { console.log(res); })
        .catch((err) => { console.log(err); })
    // 执行结果
    // 1s后:执行 promise1
    //       promise 1 失败
    // 2s后:执行 promise2
    // 3s后:执行 promise3
    
    Promise.any([promise1, promise2, promise3])
        .then((res) => { console.log(res); })
        .catch((err) => { console.log(err); })
    // 执行结果
    // 1s后:执行 promise1
    // 2s后:执行 promise2
    //       执行 promise2
    // 3s后:执行 promise3
    
    Promise.all([promise1, promise2, promise3])
        .then((res) => { console.log(res); })
        .catch((err) => { console.log(err); })
    
    // 执行结果
    // 1s后:执行 promise1
    //       promise 1 失败
    // 2s后:执行 promise2
    // 3s后:执行 promise3
    
    Promise.all([promise2, promise3])
        .then((res) => { console.log(res); })
        .catch((err) => { console.log(err); })
    // 执行结果
    // 2s后:执行 promise2
    // 3s后:执行 promise3
    //       ['promise 2 成功', 'promise 3 成功']
    
    Promise.allSettled([promise1, promise2, promise3]).then(res => {
        console.log(res);
    })
    // 执行结果
    // 1s后:执行 promise1
    // 2s后:执行 promise2
    // 3s后:执行 promise3      
    //       [
    //           { status: "rejected", reason: "promise 1 失败" },
    //           { status: "fulfilled", value: "promise 2 成功" },
    //           { status: "fulfilled", value: "promise 3 成功" }
    //       ]

     async异步函数

    异步函数:指用async关键字声明的函数默认情况下会同步执行。需要搭配await关键字使用,才能发挥异步执行的作用

    声明异步函数

    // 写法1:函数声明的写法
    async function foo1() { }
    // 写法2:表达式写法(ES5语法)
    const foo2 = async function () { }
    // 写法3:表达式写法(ES6箭头函数语法)
    const foo3 = async () => { }
    // 写法4:定义一个类,在类中添加一个异步函数
    class className {
        async foo4() { }
    }

    异步函数的返回值永远是一个 Promise 对象

    // 当异步函数内部中,未设定返回值,返回promise的状态为fulfilled,相当于return new Promise((resolve, reject) => { resolve(); }) 
    // 当异步函数内部中,返回值为普通值,返回promise的状态为fulfilled,相当于return new Promise((resolve, reject) => { resolve(设置返回值); }) ;
    // 当异步函数内部中,返回值为thenable对象(有then方法),会立即执行对象中then方法,并根据结果来决定返回的promise的状态
    // 当异步函数内部中,返回值为新的Promise,返回promise的状态取决于新的promise
    // 当异步函数内部,执行时遇到异常或者手动抛出异常时,那么,返回promise的状态处于rejected状态
    
    const foo = async function () { return 'data' }
    foo().then(res => { console.log(res); }) // data

     async/await

    在async声明的异步函数中,可以使用await关键字来暂停异步函数的执行,等待await后面的异步操作完成。

    • await 的后面是一个表达式(Promise对象),await执行完成后可以得到异步结果
    • await 会等到当前 Promise 的状态变为 fulfilled之后,才继续往下执行异步函数
    // requestData为一个异步操作
    const getData = async function () {
        const result = await requestData();
        // 执行完成await后面的异步操作,才可以继续执行下面的代码
        console.log(result);
    }
    getData();

    异步函数的异常处理

    • 方式1:通过 Promise的catch()方法捕获异常。
    • 方式2:通过 try catch捕获异常。
    function requestData1() {
        return new Promise((resolve, reject) => { reject('任务1失败'); })
    }
    
    function requestData2() {
        return new Promise((resolve, reject) => { resolve('任务2成功'); })
    }
    
    const getData = async function () {
        try {
            // requestData1 在执行时,遇到异常
            await requestData1();
            /*
            由于上面的代码在执行是遇到异常,当前任务立即终止,所以,这里虽然什么都没写,底层默认写了如下代码:
            return Promise.reject('任务1失败');
            */
    
            // 下面这两代码不会再走了
            console.log('qianguyihao1');
            await requestData2();
        }
        catch (err) {
            // 捕获异常代码
            console.log('err:', err);
        }
    }
    
    getData();

    补充:同步代码的异常处理:可通过 throw 抛出异常

    // 可以在throw后面添加表达式或者数据类型,将添加的内容抛出去。数据类型可以是:number、string、boolean、对象等。
    function sum(num1, num2) {
      if (typeof num1 !== "number") {
        throw "type error: num1传入的类型有问题, 必须是number类型"
      }
    
      if (typeof num2 !== "number") {
        throw "type error: num2传入的类型有问题, 必须是number类型"
      }
    
      return num1 + num2
    }
    sum('a', 'b');
    // 执行结果:(控制台报错) Uncaught type error: num1传入的类型有问题, 必须是number类型

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值