【八股】异步编程-ajax-promise-async-await-axios

277 阅读8分钟

ajax

基础知识

回调函数

把一个函数作为实参传给另一个函数,并在另一个函数里调用。

JS中的异常处理

错误类型

Error 所有错误的父类型

TypeError 类型错误

let b console.log(b.xxx) // TypeError:Cannot read property 'xxx' of undefined

ReferenceError 引用的变量不存在

console.log(a) // ReferenceError:a is not defined

SyntaxError 语法错误

const c = """"
// SyntaxError:Unexpected string

RangeError 不在范围内

function fn() {
  fn()
}
fn()
// RangeError:Maximum call stack size exceeded

错误处理

抛异常 throw new Error

throw new Error('errorMessage')

(抛错误又不处理的话不会往下执行的)

捕获错误并处理try catch

// 捕获处理异常
try {
  something()
} catch (error) {
  console.log(error.message)
}
console.log("我能正常执行了")

Promise

重要参考

阮一峰 es6.ruanyifeng.com/#docs/promi…

掘金的promise面试题

Promise的理解:

Promise是用来异步编程的。

从语法上看,Promise是一个对象,有统一的API,可以对各种异步操作进行处理。

从功能来说,Promise是一个容器,保存了一个(未来才会结束的)异步操作的结果。

Promise构造函数

传入一个立即执行函数executor,函数内部代码是同步执行的

函数接收两个回调resolve和reject,这两个回调是执行异步操作后的回调,把当前Promise的状态修改,并把结果PromiseResult传递出去

Promise.resolve()和Promise.reject()

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

Promise.resolve()用于吧一个已有对象包装成Promise对象

resolve里传的参数分为几种情况(参考阮一峰)

es6.ruanyifeng.com/#docs/promi…

1.不传/传固定值

返回一个Promise对象 状态为resolved 值为undefined/传的固定值

2.传Promise对象

原封不动的返回该对象

3.传thenable对象

即有then方法的对象 把对象转化为promise对象后马上执行then方法

Promise实例对象的两个属性:状态和值

状态属性:PromiseState

pending

resolved/fulfilled :执行resolve函数后

rejected:执行reject函数、或抛出一个错误时

状态一旦变为resolved或rejected就不能变化了

值(结果)属性:PromiseResult

成功的值 value

失败的值 reason

Promise.prototype.then和catch

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数(第二个可选)

catch接收rejected状态的回调

Promise的then链式调用

.then会返回一个新的Promise对象 所以可以通过.then一直链式调用

返回的新对象的值取决于then里回调函数的返回值,返回固定值则该新对象状态变为resolved,并把这个返回值传给下一个then

也可以返回一个Promise对象,进行一些异步操作,等这个Promise的对象值返回了再进入下一个then

但是执行了then和catch里的错误回调,

抛出错误的话,该新对象变为rejected,值为抛出的error

const p = Promise.reject(1).then(null,err=>{throw err}) //p rejected 1
const p = Promise.reject(1).then(null,err=>err) //p fulfilled 1
const p = Promise.reject(1).catch(err=>err) //p fulfilled 1

Promise.prototype.finally()(手写)

注意哦 原型方法是相对于实例的方法 Promise.race这种是直接调的 接受一个参数为回调函数,无论该promise实例状态如何都会执行finally的回调,相当于then方法的特例。

Promise.prototype.finally = function(callback){
    //this为p实例对象,P返回Promise构造函数
    const P = this.constructor
    return this.then(res=>P.resolve(callback()).then(res=>res))
    .catch(err=>P.resolve(callback()).then(err=>{throw err}))
    // 这两个err不是一个err 第二个err是callback的返回值吧ithink 
}

Promise.all(手写)

接受参数为一个Promise实例对象数组(如果不是promise实例,会用Promise.resolve()包装成Promise数组),返回为一个新Promise实例对象。

当所有对象都resolved的时候,返回的promise状态变为resolved,值是一个数组,包含所有对象的成功值。

当有一个对象rejected的时候,返回的promise状态变为rejected,值是第一个rejected的对象的失败值。

promiseAll = function(promises){
    promises = Array.from(promises)//确保有迭代器
    const result = []//存放返回值的结果数组
    let index = 0//记录resolve的promise的数量
    // 返回一个新的promise对象 
    return new Promise((resolve,reject)=>{
        for(const p of promises){
            p.then(res=>{
                // 遍历每个promise 成功时加入result数组
                result.push(res)
                index++
                // index到所有promise都resolve了
                if(index === promises.length-1){
                    resolve(result)
                } 
            }).catch(
                err=>reject(err)
            )
        }
    })
}

Promise.race(手写)

接受参数为一个Promise实例对象数组。

返回一个新的promise实例,该实例的状态和状态取决于该数组里第一个变为rejected或resolved的对象。

    promiseAll = function(promises){
        promises = Array.from(promises)//确保有迭代器
        const result = []//存放返回值的结果数组
        let index = 0//记录resolve的promise的数量
        // 返回一个新的promise对象 
        return new Promise((resolve,reject)=>{
            for(const p of promises){
                p.then(res=>{
                    // 遍历每个promise 成功时加入result数组
                    result.push(res)
                    index++
                    // index到所有promise都resolve了
                    if(index === promises.length-1){
                        resolve(result)
                    } 
                }).catch(
                    err=>reject(err)
                )
            }
        })
    }

Promise.allSettled(手写)

Promise.all()方法只适合所有异步操作都成功的情况,如果有一个操作失败,就会报错。

Promise.allSettled接收一个promise实例对象数组,返回一个新的Promise实例对象。

等数组中所有对象都结束后,返回的对象只会变为resolved,结果为一个数组results,包括所有promise实例对象。

promiseAllSettled =  function(promises){
        promises = Array.from(promises)//确保迭代器
        const result = []
        let index = 0
        return new Promise((resolve,reject)=>{
            for(const p of promises){
                p.finally(()=>{
                    result.push(p)
                    if(++index === promises.length) resolve(result)
                })
            }
        })
    }

Promise.any(手写)

和all相反, 只要有一个变成resolved 就返回resolved和相应成功值 所有都rejected返回rejected且抛出一个AggregateError实例,该实例有一个errors属性,保存着错误结果的数组

promiseAny = function(promises){
        promises = Array.from(promises)//确保迭代器
        const errors = []
        let index = 0
        for(const p of promises){
            p.then(res=>resolve(res))
            .catch(err=>{
                errors.push(err)
                if(++index ===  promises.length){
                    const error = new AggregateError()
                    error.errors = errors
                    throw error
                }
            })
        }
    }

Promise吃掉错误

Promise不指定错误时的回调的话 浏览器虽然会报错 但是不会影响Promise外部代码的正常运行。

(面试题)Promise控制并发数量

并发:同时进行多个任务

原生静态方法Promise.all Promise.race等 不能控制并发数量

思路:

已知一组请求url 已知生成promise请求的请求函数 已知限制最大并发数limit

1、promises数组存已生成的请求(包括pending和已完成的) queue存当前并发中的请求(且请求成功后执行.then返回index和res的数组)

2、初始时 取urls的前limit个生成请求 通过.then返回index和res的数组 加入queue队列 和 promises数组

3、for of遍历剩下的url数组 用await和queue.race 阻塞获取queue中完成的请求的index 对当前url新建请求 插入queue中对应index位置 加入promises数组

4、最后返回Promises.allsettled(promises)

async await

async 和 await 两种语法结合可以让异步代码像同步代码一样,实质是一个自执行的generator生成器函数,generator和yeild的语法糖 不用一个一个调iterator.next().value

async

async声明异步函数,函数会返回一个promise对象,该对象的结果由函数的返回值决定

async function fn() {
 // 1. 返回结果为固定值,会包装为resolved的promise对象
 // return 'yk';  // Promise {<resolved>: "yk"}
  // return; // Promise {<resolved>: undefined}
	
  // 2. 抛出错误,会返回一个失败的Promise
  // throw new Error('出错'); // Promise {<rejected>: Error: 出错

  // 3. 返回一个Promise
  return new Promise((resolve, reject) => {
    // resolve('成功的数据');
    reject('失败的数据');
  })
}

await

await用来暂停异步函数代码的执行,等待promise解决

await 必须写在async函数中,但是async函数中可以没有await

await 右侧的表达式一般为 promise 对象,如果是其他值的话会包装成promise对象(状态resolved)

await的promise失败了,就会抛出异常,需要通过try-catch捕获处理

await 右侧修饰的表达式是同步执行的 后面的代码是类似.then的异步执行的

axios

axios基于promise的http库

axios的文档 www.axios-http.cn/docs/intro

axios二次封装

juejin.cn/post/685457…

access_token / refresh_token无感刷新

思想:

1.配置在响应拦截器 在错误回调中判断acess_token是否过期 用refresh_token再发一次请求

2.为了防止多次刷新的情况出现 (在等待新的access_token返回的时候又发了请求) 添加isRefreshing标识是否正在刷新token 和requests数组 用来存放待重发的请求

具体做法:

1.在响应拦截器中 错误回调中判断返回状态码为401

2.判断isRefreshing标识 为true 返回一个Promise 将获取acess_token后重发请求的回调推入requests数组

3.判断isRefreshing标识为false 修改标识为刷新中 调用请求acess_token接口 将acess_token加入config请求头 重发请求 并依次执行requests中的请求 最后调用finally方法修改isRefreshing为false

let isRefreshing = false // 标记是否正在刷新 token
let requests = [] // 存储待重发请求的数组

instance.interceptors.response.use(response => {
    return response
}, error => {
    if (!error.response) {
        return Promise.reject(error)
    }
    if (error.response.status === 401 && !error.config.url.includes('/auth/refresh')) {
        const { config } = error
        if (!isRefreshing) {
            isRefreshing = true
            return refreshToken().then(res=> {
                const { access_token } = res.data
                setToken(access_token)
                config.headers.Authorization = `Bearer ${access_token}`
                // token 刷新后将数组的方法重新执行
                requests.forEach((cb) => cb(access_token))
                requests = [] // 重新请求完清空
                return instance(config)
            }).catch(err => {
                console.log('抱歉,您的登录状态已失效,请重新登录!')
                return Promise.reject(err)
            }).finally(() => {
                isRefreshing = false
            })
        } else {
            // 返回未执行 resolve 的 Promise
            return new Promise(resolve => {
                // 用函数形式将 resolve 存入,等待刷新后再执行
                requests.push(token => {
                    config.headers.Authorization = `Bearer ${token}`
                    resolve(instance(config))
                })  
            })
        }
    }
    return Promise.reject(error)
})

吹牛皮时间 把二次封装aixos + 无感刷新token token写成jwt(但jwt应该是后端实现的 所以jwt应该要再补一补)