javaScript2

sclweb / 2023-08-15 / 原文

同步异步 -promise

1.同步和异步

同步:代码从上往下依次执行(编译过后)

异步:异步代码要等到当前文件中所有同步代码执行完成以后再单独依次执行异步代码

2.常见的异步场景

1)定时器

2)事件处理函数

3)异步ajax

4)异步回调

以上的异步逻辑都被包含在回调函数中

3.回调地狱

回掉函数嵌套回调函数,层层嵌套的现象,称之为【回调地狱】

4.promise--强制异步按照队列执行

promise是什么?

Promise是用来【对异步操作进行管理】的,解决了回调地狱问题 ,可以让我们用同步的方式来编写异步代码。ES6规定,Promise是一个内置的构造函数,用“new Promise构造函数”的形式来生成Promise实例。

通过“new Promise构造函数”来得到Promise实例对象

1)状态:标识异步操作的状态,分:进行中、已成功、已失败

2)队列:当状态切换时(切换到已成功或已失败)执行队列中的回调。

3)方法:继承自Promise原型的众多方法,用来对异步操作进行管理。

 

Promise本身是同步的(即实例化对象的时候为同步,但是当定时器处于promise内部时,promise实例化的时候同步执行,定时器异步执行,这时候如果定时器内存在错误,catch也捕获不到了,因为错误已经不在promise作用域内)

$ docker run \
  --name jenkins-blueocean \
  -d \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins-data:/var/jenkins_home \
  jenkinsci/blueocean
93f88d6ca2129fae971298e98e20091570f6903463b203a9235660856f63020e

 

 

5.promise语法

then/catch 都是Promise原型方法,接收Promise成功和失败的结果。

catch捕获链条之前所有的错误

let p1=new Promise((resolve,reject)=>{
	if(true){
  	resolve(1)
  }else{
  	reject("失败")
  }
})
p1.then(res=>{
	//成功的回调
}).catch(err=>{
	//失败的回调
})

6.promise状态流转

状态流转取决于 是否调用resolve和reject

1)pending--->fulfilled(或称resolve) (已成功)

2)pending--->rejected(已失败)

特点:

1.状态不受外界影响,只由结果决定。

2.状态不可逆,一旦发生就不再改变,任何时候都可以获得这个结果。(比如一个promise,三个resolve 只有第一个有效。)

7.then和catch方法 --异步

then调用的前提是状态改变为resolve。

then:接收处理【Promise】 结果(成功,也可以接收失败),是推荐用catch来接收reject返回的失败错误。因为then无法捕获同为参数的resolve回调中抛出的错误。then方法调用后,不管你是否return都会返回一个新的promise。

//1.then方法调用后返回一个新的promise

//2.如果then方法没有传回调,那么默认将上一个promise结果数据包装到一个新的promise内返回出去

//3.如果then内部做了相关处理,并且return了 ,那么就将return的这个值作为新的promise的状态结果返回回去

catch:指定发生错误时的回掉函数,回掉函数的参数为【异步执行过程中】抛出来的错误信息。

8.Promise解决回调地狱

回调函数返回值会传入下一个then内,若返回promise实例,则下一个then 接收到promise实例状态结果。

const p1 = new Promise((resolve, reject) => {
  $.ajax({
    url: "./1.json",
    type: "get",
    data: "",
    success: (data) => {
      resolve(data)
    }
  })
})
p1.then(data =>new Promise((resolve, reject) => {//箭头函数,简写 {} ,省掉return
  $.ajax({
    url: "./2.json",
    type: "get",
    data: data.id,
    success: (data) => {
      resolve(data)
    }
  })
})
).then(data =>new Promise((resolve, reject) => { //箭头函数,简写 {} ,省掉return
  $.ajax({
    url: "./3.json",
    type: "get",
    data: data.code,
    success: (data) => {
      resolve(data)
    }
  })
})
).then(data => console.log(data.code))  //箭头函数,简写 {}

简化版

//封装promise函数
let pro=(url,type,data)=>new Promise((resolve,reject)=>{ //箭头函数 简写{} 和return
	$.ajax({
  	url,
    type,
    data,
    success:(data)=>{
    	resolve(data)
    }
  })
})
//调用
pro("./1.json","get",{})
.then(data=>pro("./2.json","get",{id:data.id})) //箭头函数 简写{} 和return
.then(data=>pro("./3.json","get",{id:data.id})) //箭头函数 简写{} 和return
.then(data=>console.log(data.code))//箭头函数 简写{}

10.finally() --promise原型方法

和then catch一样的用法,指定不论promise状态结果如何,都会执行该方法内部的回调,且方法的回调不接收任何参数。可以用于不管是成功还是失败的时候都要做的操作,比如关闭提示框等。

let p1=new Promise((resolve,reject){
	if(true){
		resolve(1)
	}else{
  	reject("失败")
  }	                   
})
p1.then(res=>{
	console.log(res)
}).catch(err=>{
	console.log(err)
}).finally(()=>{
	console.log("执行完了") //此时可以在此操作不管失败试试成功都要做的操作。
})

11.all()方法 ---promise 静态方法

作用:将多个promise实例包装成一个新promise实例,接受一个数组(也可以是lterator数据结构),用于处理多个promise实例。

1)如果所有promise结束状态都是fulfilled,此时所有promise的成功结果组成数组传递到all执行结束后的then指定的回调。

2)如果前面的promise某个状态为rejected,则将这个promise的rejected结果传递到all执行结束后的catch指定的回调。

3)在不关心异步执行顺序和结果时,使用all方法集中管理promise不失为一个好方法。

const p1 = new Promise((resolve,reject)=>{
  if(true){
    resolve(1)
  }else{
    reject('错误')
  }
})

const p2 = new Promise((resolve,reject)=>{
  if(true){
    resolve(2)
  }else{
    reject('错误')
  }
})

const p3 = new Promise((resolve,reject)=>{
  if(true){
    resolve(3)
  }else{
    reject('错误')
  }
})

const all_promise = Promise.all([p1,p2,p3]); //传入一个数组

all_promise.then(res=>{ //全部成功接收数组promise的全部状态结果,结构为数组
  console.log(res)
}).catch(err=>{
  console.log(err) //有一个为rejected状态,捕获到这个状态
})

 

12.race()方法 ---promise 静态方法

作用:管理多个promise时获取最先传递的结果。

和all一样,race也接受多个promise组成的数组结构(也可以是lterator数据结构),但race不会等待所有的异步执行完毕,而是拿到最先发生状态改变的promise的结果就不再接受后续的promise实例的结果了。--有点鸡肋

const p1 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		console.log('p1执行了')
		resolve(1)
	},1000)
})

const p2 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		console.log('p2执行了')
		resolve(2)
	},5000)
})

const p3 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		console.log('p3执行了')
		resolve(3)
	},2000)
})
const p = Promise.race([p1,p2,p3]);
// p1,p2,p3其中某一个率先完成,将结果传入p的then或者catch
p.then(res=>console.log(res)).catch(err=>console.log(err))

13event-loop事件队列(拓展面试题)

js是一门事件驱动的语言,这里的事件不仅仅包括我们平时理解的click、mouseover等等,还有定时器,promise等,js的代码依靠这些事件来实现异步,那么这些代码是如何执行的呢?

【注意】event--loop原理,高端团队面试必问。

js代码在执行前要将全部代码【编译】一次,其中包括变量提升,任务队列推送等。当一切准备好以后,就可以开始执行代码了。

1)js事件队列

js任务分为同步任务队列异步任务队列,异步任务队列又分为:宏任务微任务

代码执行时,将同步代码推送到同步任务队列依次执行,将异步代码推送到异步任务队列准备起来。

当同步任务执行完后,再将异步队列中的任务推送到同步队列中依次执行。

2)js异步队列

宏任务:setTimeout、setInterval等

当遇到宏任务的时候推送到宏任务队列。

微任务:promise、then

当遇到微任务的时候会将微任务推送到微任务队列。

3)js队列执行顺序

1-先执行同步任务

2-同步任务结束后,执行一次微任务(将微任务队列清空)

3-然后依次执行宏任务,每执行一次宏任务再去微任务队列查看是否有新的微任务,有就清空。

 

 

axios库-async与await

1.什么是axios?

Axios 是一个基于 promise 的 HTTP 请求库,用在浏览器和 node.js 中。由于axios基于promise,我们的ajax请求代码可以变得更优雅,且可以做更细致的控制。

2.使用

中文文档地址: http://www.axios-js.com/

3.案例

get 参数名params post参数名 data

例1:
axios({
	method: 'post',
	url: '/user/12345',
	data: {
		name: '源宝'
	}
})
.then((res) => {
	// 请求成功
})
.catch((err) => {
	// 请求失败或出错
})

例2:
axios.get('url1')
.then((data) => axios.get('url2', {
	params: { id: data.id }
}))
.then((data) => axios.get('url3', {
	params: { code: data.code }
}))
.then((data) => {
	console.log('终于拿到', data)
})

4.async和await

async:异步 await:等待

async和await是ES7新特性中提供的新特性,用于进一步完善异步代码的操作。

【注意】

1)async修饰函数,表示该函数内有异步操作。被async修饰的函数称为---async函数。async修饰的函数返回一个promise

2)await只能在async内使用,用于【直接】修饰Promise,后面的代码需要等待promise执行结束才执行。

await本质是等带promise的结果状态改变,只有状态改变时,await才会有效果。

await也可以修饰基本数据(数字,字符串等),但被修饰的数据会转换成立即resolve的promise

3)async函数返回promise

例:

let run1=()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
      console.log(1)
      resolve(1)
    },2000);
  })
}

let run2=()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
      console.log(2)
      resolve(2)
    }, 1000);
  })
}

async function runtimer() {
  await run1(); //修饰await本质上是 等待promise的状态结果
  await run2();//修饰await本质上是 等待promise的状态结果
  console.log(3)
}
runtimer()
//执行结果 123  
let p1=new Promise((resolve,reject)=>{
	setTimeout(()=>{
  	resolve(1)
  },2000)
})
let p2=new Promise((resolve,reject)=>{
	setTimeout(()=>{
  	resolve(2)
  },1000)
})

//使用async和await修饰
async function async_p(){
	await p1.then(res=>console.log(res));
  await p2.then(res=>console.log(res));
}
async_p();

5.让异步代码按队列执行的方法

都基于promise

1)promise中then的链式调用

2)async和await绑定使用

 

6.企业级封装ajax 请求--重点

封装ajax请求,实现代码复用,让项目中非常多的ajax请求实现轻松管理和维护,

1)准备工作:

a)安装qs库--yarn add qs 或者 npm i qs --主要用于将对象转换为url参数形式,或者反向

示例:

import qs from 'qs'; //引入qs

const url = 'method=query_sql_dataset_data&projectId=85&appToken=7d22e38e-5717-11e7-907b-a6006ad3dba0';
// 转为对象
qs.parse(url) 

const a = {name:'hehe',age:10};
// 转为url参数形式
qs.stringify(a)

b)安装axios --yarn add axios 或者npm i axios --基于promise的http请求库

2)utils文件中建 request.js --封装第一层 -请求工具层--

知识点:Object的hasOwnProperty()方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。

关于拦截器:

作用-

1.修改请求头的一些配置项

2.给请求的过程添加一些请求的图标

3.给请求添加参数(比如token)

实例运用:关于token(接口鉴权)

1.用户通过用户名和密码发送请求。

2.程序验证。

3.程序返回一个签名的token 给客户端。

4.客户端储存token,并且每次用于每次发送请求。

5.服务端验证token并返回数据。

关于Message --引入的element-ui的一个方法,用于提示信息。

//引入axios 和qs 
import axios from "axios"
import qs from "qs"  //用于转换请求参数的格式  请先安装  yarn add qs 
import local from './local' //引入自定义的本地存储模块
import { Message } from 'element-ui'; //引入elementui的message功能 

/* 封装 通用的请求url */
axios.defaults.baseURL = "http://127.0.0.1:5000"

// 请求拦截器【请求发送出去之前】
axios.interceptors.request.use((config) => {
  // 取出令牌【token】--登录以后就存储到本地了
  let token = local.get('t_k') || ""   //如果t_k 也就是token存在就让他等于变量token,不存在就等于空
  if (token) {
    // 挂在请求头上--Authorization属于请求头上的属性,用来放token的 
    config.headers.Authorization = token;
  }
  return config
}, (err) => {
  Promise.reject(err) 
})

// 响应拦截器【后端响应数据接收到之前】
axios.interceptors.response.use((response) => {
  let res = response && response.data; //如果相应数据存在,变量res就等于响应数据对象下的data属性
  
  // 判断返回的数据要包含 code 字段 和 msg 字段
  // Object的hasOwnProperty()方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。
  if (res.hasOwnProperty('code') && res.hasOwnProperty('msg')) {
    let { code, msg } = res; // 获取code 和 msg的值 --解构赋值,只拿取res中的code和msg
    // 如果等于0 代表成功
    if (code === 0) {
      console.log(this)
      Message({  //引入的element-ui中的Message方法就奏效了
        type: 'success',  //type-主题,提示文字的样式:success/warning/info/error
        message: msg, //提示的文字-这里的msg为后端响应过来的
      })
    } else if (code === 1) {
      // 否则 代表失败
      Message.error(msg)
    }
  }
  return response
}, (err) => {
  Promise.reject(err)
})


// 封装通用的get请求 和 post请求
const request = {
  get(url, data = {}) { //data={}参数的意思为:如果没有参数,就让它等于空对象
    return new Promise((resolve, reject) => { //get()方法返回一个promise  下一层封装调用的时候就得到promise
      axios.get(url, {
        params: data
      }).then(response => {
        resolve(response.data) // 成功的数据
      }).catch(err => {
        reject(err) // 失败
      })
    })
  },
  post(url, data = {}) {
    return new Promise((resolve, reject) => {
      axios.post(url, qs.stringify(data)).then(response => {
        resolve(response.data) // 成功的数据
      }).catch(err => {
        reject(err) // 失败
      })
    })
  }
}

// 暴露出去
export default request;

 

3)src下建api文件分类xxx.js --封装第二层-接口层 --大模块相关的ajax请求统一放在这

/* 账号相关所有ajax请求 */

// 引入上一层封装的请求工具request
import request from '@/utils/request'

// 用户登录
export const checkLogin = (data) => { //封装第二层的接口,并暴露出去,下一层组件层引入后,直接调用传参就可以使用了。
  return request.post('/users/checkLogin', data) //data形参-请求的参数,下一层使用的时候传入,return 出去的是一个promise,下一层会用await来修饰
}

// 添加账号
export const addAccount = (data) => {
  return request.post('/users/add', data)
}

// 获取账号列表
export const getAccountList = (data) => {
  return request.get('/users/list', data)
}

// 删除账号
const delAccount = () => {
}

 

4)各组件中引入使用xxx.js ,并调用相关方法来发送请求。

// 对整个表单进行验证
this.$refs.loginForm.validate(async (valid) => { async 修饰函数 
  if (valid) { //验证通过 解构赋值
    let { code, msg, role, token } = await checkLogin(this.loginForm)  await修饰promise--修饰的本质就是等待promise的状态流转,必须和async连用。
    // 如果等不到promise状态流转,下面的代码将不会再执行。
    // 判断
    if (code === 0) { //登录请求成功
      local.set('t_k', token) // 把token存入本地  //也就登录需要存token,用set()记得引入自定义的local模块
      this.$router.push('/home') // 跳转到后端首页
    }
  } else {
    console.log('不可以提交,因为前端验证没有通过')
    return
  }

 

5)封装过程中的巧技:

有意思的语法:

res&&res.data --如果res存在 就取值res.data

函数传参:data={} 表示如果没有参数默认传入空对象

为何import 引入自定义js要加上{}?

只用export 暴露的要加{}

export defaule不用--在模块导出时,可能会存在默认导出。同样的,在导入时可以使用import指令导出这些默认值。