异步操作学习笔记

18 篇文章 23 订阅
16 篇文章 1 订阅

1.异步操作

1.1 什么是单线程?

JavaScript是单线程语言。只有一个线程,同时只能做一件事,两段js不能同时执行。

1.2 为什么JavaScript是单线程?

为了避免DOM渲染冲突;

1.3 异步出现的原因?

解决因同步执行代码阻塞时间过长而导致的页面卡死情况

2.Event Loop(事件轮询)

2.1 Event Loop 的理解

  • Event Loop是javascript的执行机制,用于等待发送消息的事件,是实现异步的具体解决方案

  • 在程序中设置两个线程:一个负责程序本身的运行,称为"主线程";另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为"EventLoop线程"(可以译为"异步线程")。

2.2 Event Loop 运行流程

ES6标准中,异步任务又分为两种类型,宏任务(macrotask)和微任务(microtask)。

宏任务:由宿主环境提供,在浏览器环境执行,比如:setTimeoutsetInterval网络请求、用户I/O、script(整体代码)、UI rendering、setImmediate(node)。

微任务:语言标准提供,在js引擎环境执行,如:process.nextTick(node环境中,要先于其他微任务执行)、Promise、Object.observe、MutationObserver。

  1. 同步代码(主线程)直接执行;
  2. 异步函数先放在异步队列中。任务队列的读取顺序是先读取所有微观任务队列执行后再读取一个宏观任务队列,再读取所有微观任务队列,再读取一个宏观任务队列…
  3. 待同步函数执行完毕,轮询执行 异步队列中的函数;
  4. 以上步骤不断重复执行,就形成了事件轮询;
    在这里插入图片描述

3. settimeout和setinterval

settimeoutsetinterval也是异步操作,该操作里面的内容会延迟操作。

4. XHR(XMLHttpRequest)

  • XMLHttpRequest 用于在后台与服务器交换数据
  • 通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。
  • 这允许网页在不影响用户操作的情况下,更新页面的局部内容。
  • XMLHttpRequestAJAX 编程中被大量使用。
    this.xhr = new XMLHttpRequest(); // 创建 XMLHttpRequest 对象
    this.xhr.open('post', url, true); // post方式,url为服务器请求地址,true 该参数规定请求是否异步处理。
    this.xhr.onload = evt => this.uploadComplete(evt); // 请求完成
    this.xhr.onloadstart = () => this.progressFunction(uuid); // 请求开始
    this.xhr.upload.onprogress = () => this.progressFunction(uuid); // 请求中
    this.xhr.onerror = this.uploadFailed; // 请求失败
    this.xhr.send(formData); // 开始上传,发送form数据

5 AJAX

Asynchronous JavaScript + XML(异步JavaScript和XML)。

Ajax 是一个技术统称,是一个概念模型,它囊括了很多技术,并不特指某一技术,它很重要的特性之一就是让页面实现局部刷新

$.ajax({
    type : "GET",  //提交方式
    url : web2, //路径,www根目录下
    datatype: "json", //数据类型
    async: false, //是否为异步操作
    success : function(result) { //返回数据根据结果进行相应的处理
        var accountname = result.attributes.accountname;
        alert(accountname);
        
    }
});

XMLHttpRequest 模块就是实现 Ajax 的一种很好的方式,利用 XMLHttpRequest 模块实现 Ajax:

<body>
  <script>
    function ajax(url) {
      const xhr = new XMLHttpRequest();
      xhr.open("get", url, false);
      xhr.onreadystatechange = function () {
        // 异步回调函数
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            console.info("响应结果", xhr.response)
          }
        }
      }
      xhr.send(null);
    }
    ajax('https://smallpig.site/api/category/getCategory')
  </script>
</body>

6. promise

  • 是ES6提出的异步编程的一种解决方案。
  • 从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

6.1 Promise 对象

  • 用同步的方式来书写异步代码
  • Promise 让异步操作写起来,像在写同步操作的流程,解决了回调地狱的问题(不必一层层地嵌套回调函数)
  • 改善了可读性,对于多层嵌套的回调函数很方便
  • 充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口
function fn() {
  return new Promise((resolve, reject) => {
    resolve(result) //成功时调用 resolve
    reject(error)   //失败时调用 reject
  })
}

fn().then(success, fail).then(success2, fail2).catch(fail3);

6.2 Promise 状态

Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。

Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。

const p1 = new Promise(function(resolve,reject){
    resolve('success1');
    resolve('success2');
}); 
const p2 = new Promise(function(resolve,reject){  
    resolve('success3'); 
    reject('reject');
});
p1.then(function(value){  
    console.log(value); // success1
});
p2.then(function(value){ 
    console.log(value); // success3
});

6.3 then 方法

then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。

在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。

可以添加多个回调函数,它们会按照插入顺序并且独立运行。

const p = new Promise(function(resolve,reject){
  resolve(1);
}).then(function(value){ // 第一个then // 1
  console.log(value);
  return value * 2;
}).then(function(value){ // 第二个then // 2
  console.log(value);
}).then(function(value){ // 第三个then // undefined
  console.log(value);
  return Promise.resolve('resolve'); 
}).then(function(value){ // 第四个then // resolve
  console.log(value);
  return Promise.reject('reject'); 
}).then(function(value){ // 第五个then //reject:reject
  console.log('resolve:' + value);
}, function(err) {
  console.log('reject:' + err);
});

6.4 Promise.all

  • Promise.all 需要传入一个数组,数组中的元素都是 Promise 对象。
  • 当这些对象都执行成功时,则 all 对应的 promise 也成功,且执行 then 中的成功回调。
  • 如果有一个失败了,则 all 对应的 promise 失败,且失败时只能获得第一个失败 Promise 的数据。
const p1 = new Promise((resolve, reject) => {
  resolve('成功了')
})
const p2 = Promise.resolve('success')
const p3 = Promise.reject('失败')

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

Promise.all([p1, p3, p2]).then((result) => {
  //未被调用
}).catch((error) => {
  console.log(error)  //"失败"
});

6.5 Promise.race

Promise.race() 里面哪个 Promise 对象最快得到结果,就返回那个结果,不管结果本身是成功状态还是失败状态。

const p1 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 500, "one");
});
const p2 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, "two");
});
Promise.race([p1, p2]).then((result) => {
  console.log(result); // "two"
  // 两个都完成,但 p2 更快
});

const p3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, "three");
});
const p4 = new Promise(function(resolve, reject) {
  setTimeout(reject, 500, "four");
});
Promise.race([p3, p4]).then((result) => {
  console.log(result); // "three"
  // p3 更快,所以它完成了
}, (error) => {
  // 未被调用
});

const p5 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, "five");
});
const p6 = new Promise(function(resolve, reject) {
    setTimeout(reject, 100, "six");
});
Promise.race([p5, p6]).then((result) => {
  // 未被调用
}, error => {
  console.log(error); // "six"
  // p6 更快,所以它失败了
});

7 fetch

Fetch 是在 ES6 出现的,它使用了 ES6 提出的 promise 对象。它是 XMLHttpRequest 的替代品。

Fetch 是一个 API,它是真实存在的,它是基于 promise 的。

fetch('./api/some.json')
  .then(
    function(response) {
      if (response.status !== 200) {
        console.log('Looks like there was a problem. Status Code: ' +
          response.status);
        return;
      }

      // Examine the text in the response
      response.json().then(function(data) {
        console.log(data);
      });
    }
  )
  .catch(function(err) {
    console.log('Fetch Error :-S', err);
  });

7.1 fetch优势

  1. 语法简洁,更加语义化
  2. 基于标准 Promise 实现,支持 async/await
  3. 同构方便,使用 isomorphic-fetch
  4. 更加底层,提供的API丰富(request, response)
  5. 脱离了XHR,是ES规范里新的实现方式

7.2 fetch存在问题

  1. fetch是一个低层次的API,你可以把它考虑成原生的XHR,所以使用起来并不是那么舒服,需要进行封装。
  2. fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: ‘include’})
  3. fetch没有办法原生监测请求的进度,而XHR可以

7.3 取消 fetch 请求

使用情况:两次查询请求相隔时间很短时,由于接口异步,第一次请求可能会覆盖第二次请求返回数据,所以需要在第二次请求前先将第一次请求中止

  • 创建一个 AbortController实例
  • 这个实例拥有一个 signal 属性
  • 把这个 signal 作为 fetch 请求的选项
  • 调用 AbortControllerabort 属性以取消所有使用这个信号的 fetch 请求。
  // 获取中断信号
  getAbortSignal(){
    if(window.AbortController) {
      window._abortController = new AbortController();
    }
    return window._abortController?.signal || null;
  },

  // 中断未完成 fetch 请求
  abortFetchRequest() {
    if(window._abortController?.abort) {
      window._abortController.abort();
    }
  },

  // fetch函数网络请求的封装
  fetchRequest(url, params, successCallBack, failureCallBack, isRefreshToken = true, isAbortFetch = false) {
    if(isAbortFetch) {
      this.abortFetchRequest();
    }
    if (!isRefreshToken || tokenUtil.tokenEnable()) {
      let token = {};
      if (localStorage.getItem("token")) {
        token = JSON.parse(localStorage.getItem("token"));
      }
      params.nonce = this.generateUUID();
      params.timestamp = new Date().getTime();
      params.sign=CryptoJS.MD5(params.nonce + params.timestamp + config.replySecret).toString();
      const { access_token = "" } = token;
      const postParams = {
        method: 'POST',
        mode: 'cors',
        headers: {
          Authorization: access_token,
          Accept: 'application/json',
          'Content-Type': 'application/json;charset=UTF-8',
        },
        body: JSON.stringify(params),
        credentials: 'include',
        signal: this.getAbortSignal(),
      };
      fetch(url, postParams)
        .then(response => {
          if (isRefreshToken && response.status === 401) {
            tokenUtil.refreshTokenForFetch(url, params, successCallBack, failureCallBack);
            return;
          }
          if (response.ok) {
            response.json().then((json) => {
              // 网络连接成功的回调
              successCallBack(json);
            });
          } else {
            failureCallBack('请求失败');
          }
        })
        .catch(err => {
          if(!err?.toString().includes('AbortError')) { // 手动中断请求不抛异常
            failureCallBack(`请求异常${err}`);
          }
        });
    } else {
      tokenUtil.refreshTokenForFetch(url, params, successCallBack, failureCallBack);
    }
  },

8. axios

axios 是一个基于 promise 的 HTTP 封装库,可以用在浏览器和 node.js 中。本质也是对XHR的封装,底层可以选择xhr或者fetch进行请求

使用Axios的好处:

  1. 在浏览器中发送XMLHttpRequests请求
  2. 在node.js中发送http请求
  3. 支持Promise API
  4. 拦截请求和响应
  5. 转换请求和相应数据

8.1 axios基本使用

    # 1. 发送get 请求 
	axios.get('http://localhost:3000/adata').then(function(ret){ 
      #  拿到 ret 是一个对象      所有的对象都存在 ret 的data 属性里面
      // 注意data属性是固定的用法,用于获取后台的实际数据
      // console.log(ret.data)
      console.log(ret)
    })
	# 2.  get 请求传递参数
    # 2.1  通过传统的url  以 ? 的形式传递参数
	axios.get('http://localhost:3000/axios?id=123').then(function(ret){
      console.log(ret.data)
    })
    # 2.2  restful 形式传递参数 
    axios.get('http://localhost:3000/axios/123').then(function(ret){
      console.log(ret.data)
    })
	# 2.3  通过params  形式传递参数 
    axios.get('http://localhost:3000/axios', {
      params: {
        id: 789
      }
    }).then(function(ret){
      console.log(ret.data)
    })
	#3 axios delete 请求传参     传参的形式和 get 请求一样
    axios.delete('http://localhost:3000/axios', {
      params: {
        id: 111
      }
    }).then(function(ret){
      console.log(ret.data)
    })

	# 4  axios 的 post 请求
    # 4.1  通过选项传递参数
    axios.post('http://localhost:3000/axios', {
      uname: 'lisi',
      pwd: 123
    }).then(function(ret){
      console.log(ret.data)
    })
	# 4.2  通过 URLSearchParams  传递参数 
    var params = new URLSearchParams();
    params.append('uname', 'zhangsan');
    params.append('pwd', '111');
    axios.post('http://localhost:3000/axios', params).then(function(ret){
      console.log(ret.data)
    })

 	#5  axios put 请求传参   和 post 请求一样 
    axios.put('http://localhost:3000/axios/123', {
      uname: 'lisi',
      pwd: 123
    }).then(function(ret){
      console.log(ret.data)
    })

8.2 axios全局配置

开发中,很多参数是固定的. 可以进行一些抽取, 也可以利用axiox的全局配置

axios.defaults.baseURL = 'tp://000.000'
axios.defaults.headers.post['Content-Type'] = 'xxxxx'

export default {
    name: 'app',
    created () {
        // 提取全局的配置
        axios.defaults.baseURL = 'http://000.000'
        
        // 发送并发请求  
        axios.all([
            axios.get('/xxxx'), 
            axios.get('/xxxxx', {
                params: {
                    type: 'sell', 
                    page: 1
                }}
            )
        ]).then(axios.spread((rea1, res2) => {
            console.log(res1);
            console.log(res2);
        }))    
    }
}

8.3 常见的配置选项

请求地址   url:/user’,
请求类型   method:get,
请根路径   baseURL: ‘http://www.mt.com/api’,
请求前的数据处理   transformRequest:[function(data){}],
请求后的数据处理   transformResponse: [function(data){}],
自定义的请求头   headers:{‘x-Requested-With’:‘XMLHttpRequest’},
URL查询对象   params:{ id: 12 },
查询对象序列化函数 
paramsSerializer: function(params){ }
request body
data: { key: ‘aa’},
超时设置s   timeout: 1000,
跨域是否带Token   withCredentials: false,
自定义请求处理   adapter: function(resolve, reject, config){},
身份验证信息   auth: { uname: ‘’, pwd:12},
响应的数据格式 json / blob /document /arraybuffer / text / stream
responseType: ‘json’,

8.4 同时发送两个请求

使用axios.all, 可以放入多个请求的数组. axios.all([]) 返回的结果是一个数组,使用 axios.spread 可将数组 [res1,res2] 展开为 res1, res2

import axios from 'axios'

export default {
    name: 'app',
    created () {
        // 发送并发请求  
        axios.all([axios.get('tp://000.000/xxxx'), 
            axios.get('tp://000.000/xxxx', {params: {type: 'sell', page: 1}})])
        .then(axios.spread((rea1, res2) => {
            console.log(res1);
            console.log(res2);
        }))    
    }
}

8.5 axios实例

某些请求需要使用特定的baseURL或者timeout或者content-Type等.

const inatancel = axios.create({
    baseURL: 'http: //111.2222',
    timeout: 5000
})

instancel({
    url: '/home/mutidata'
}).then(res => {
    console.log(res);
})

instancel({
    url: '/home/index'
}).then(res => {
    console.log(res);
})

8.6 axios封装

建立一个network文件夹, 在里面建立一个request.js文件

export function request (config) {
    // 1. 创建axios的实例 
    const instance = axios.create({
        baseURL: 'http://111.222.33.44',
        timeout: 5000
    })
    
    // 2. 发送真正的网络请求
    // 返回的 instancel(config)就是一个promise
    return instancel(config)
}

在使用的文件中. 封装request模块

import {request} from './network/request';

request({
    url: '/home/multidata'
}).then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
})

8.7 axios拦截器

axios提供了拦截器,用于我们在发送每次请求或者得到相应后,进行对应的处理

// axios.config.ts
export function request (config) {
    // 1. 创建axios的实例 
    const instance = axios.create({
        baseURL: 'http://111.222.33.44',
        timeout: 5000
        ...config
    })
    
    // 2. axios 请求拦截器 
    // config可以随便命名
    instance.interceptors.request.use(config => {
    	// 在发送请求之前做些什么
        // 不返回, 调用的时候会进入err
        return config
    }, err => {      // 发送都没成功
        console.log(err);
    })
    
    // 3. axios 响应拦截器   
    instance.interceptors.response.use(response=> {
      // 对响应数据做点什么
      console.log("response:", response);
      const { code, data, message } = response.data;
      if (code === 200) return data;
      else if (code === 401) {
        jumpLogin();
      } else {
         Message.error(message);
         return Promise.reject(response.data);
      }
    },
  );
    
    // 4. 发送真正的网络请求
    return instancel(config)
}

8.8 使用 cancel token 取消请求

可以使用 CancelToken.source 工厂方法创建 cancel token,像这样:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
     // 处理错误
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');

还可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c;
  })
});

// cancel the request
cancel();

9. 捕获异步操作的异常

9.1 捕获Promise中的异常

在JavaScript中,你可以使用.catch()方法来捕获Promise中的异常:

function asyncFunction() {  
    return new Promise((resolve, reject) => {  
        // 模拟异步操作  
        setTimeout(() => {  
            if (/* 一切正常 */) {  
                resolve('操作成功');  
            } else {  
                reject(new Error('发生错误'));  
            }  
        }, 1000);  
    });  
}  
  
asyncFunction()  
    .then(result => {  
        console.log(result);  
    })  
    .catch(error => {  
        console.error('捕获到异常:', error);  
    });

9.2 使用async/await

使用async/await时,你可以像处理同步代码一样使用try/catch来捕获异常:

async function asyncFunction() {  
    // 模拟异步操作  
    return new Promise((resolve, reject) => {  
        setTimeout(() => {  
            if (/* 一切正常 */) {  
                resolve('操作成功');  
            } else {  
                reject(new Error('发生错误'));  
            }  
        }, 1000);  
    });  
}  
  
async function main() {  
    try {  
        const result = await asyncFunction();  
        console.log(result);  
    } catch (error) {  
        console.error('捕获到异常:', error);  
    }  
}  
  
main();
async function doSomething() {  
  try {  
    const result = await someAsyncFunction();  
    // 处理结果  
  } catch (error) {  
    // 处理错误  
    console.error('An error occurred:', error);  
  }  
}  
  
doSomething();

练习题

setTimeout(() => {
  console.log(1)
  new Promise(resolve => {
    setTimeout(() => {
      console.log(5)
      resolve(5);
    }, 0)
    console.log(6)
  }).then(res => {
    console.log(7)
  });
  new Promise(resolve => {
    setTimeout(() => {
      console.log(5)
      resolve(5);
    }, 0)
    console.log(6)
  }).then(res => {
    console.log(7)
  });
}, 0)
  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值