前端必备性能优化

#Promise async/await总结

2020-03-14  本文已影响0人  扶不起的蝌蚪

1. JS中的异步操作哪些?

for (var i = 0; i <10; i++) {  
  setTimeout(()=>{  
    console.log(i);        
  }, 0);
}
//=>10 //=>10 //=>10 //=>10 //=>10 //=>10 //=>10 //=>10 //=>10 //=>10  

问:下面打印的结果是?

console.log('1')
setTimeout(()=>{console.log('2')},0)
console.log('3')
------------------------
// 1
// 3
// 2
for (var i = 0; i < tabList.length; i++) {
    //tabList[i] <=>每一轮循环当前要操作的LI DOM对象
    tabList[i].onclick = function () {
        alert(i);
        changeTab(i);//=>需要把当前点击的这个LI的索引传递进来
    }
}*/
当绑定完毕事件后,进行点击发现i全都是3了

2. 处理异步的方式有哪些?

2.1 回调函数

回调callback是一个函数被作为一个参数传递到另一个函数里,在那个函数执行完后再执行。( B函数被作为参数传递到A函数里,在A函数执行完后再执行B )
假定有两个函数f1和f2,f2等待f1的执行结果,f1()-->f2();如果f1很耗时,可以改写f1,把f2写成f1的回调函数:

function f1(callback){
  setTimeout(function () {
    callback(); // f1的任务代码
  }, 1000);
}
f1(f2);  // 执行

采用回调的方式,把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。

回调函数是异步编程最基本的方法,其优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,流程会很混乱,而且每个任务只能指定一个回调函数。

注意 区分 回调函数和异步,回调是实现异步的一种手段,并不一定就是异步。
回调也可以是同步,如:

//下面没有任何有关异步的函数操作,所以是同步的
function A(callback){
    console.log("I am A");
    callback();  //调用该函数
}
function B(){
   console.log("I am B");
}
A(B);

2.2 事件监听

采用事件驱动模式,任务的执行不取决代码的顺序,而取决于某一个事件是否发生。
监听函数有:onbindlistenaddEventListenerobserve
举例,为f1绑定一个事件(jquery写法):f1.on('done',f2);即当f1发生done事件,就执行f2。

function f1(){
    settimeout(function(){
       // f1的任务代码
       f1.trigger('done');  // 执行完成后,立即触发done事件,从而开始执行f2
    },1000);
}

优点:易理解,可绑定多个事件,每一个事件可指定多个回调函数,可以去耦合,有利于实现模块化
缺点:整个程序都要变成事件驱动型,运行流程会变得不清晰

2.3 发布订阅

2.4 Promise

2.4.1 Promise基本概述
2.4.2 基本用法

new Promise时候会立刻执行

new Promise.png

控制台能反映当前promise的状态,因为我没有resolve或reject,状态就不会发生改变

new Promise((resolve,reject)=>{console.log('1')})
//=> 1

里面加入resolve我们看看他的状态


resolve.png

可以看到Promise的状态变成了resolve

return new Promise

return new Promise一般情况下我们把Promise封装到一个函数中,在我们需要处理异步的时候进行调用,就需要进行return操作,当我们调用函数的时候实际上是进行上面的new Promise立即执行,new promise还是同步处理的

const ajaxPromise=  param => {
  return new Promise((resovle, reject) => {
    $.ajax({
      ...
      "success": res => {
        resovle(res);
      },
      "error": err => {
        reject(err);
      }
    })
  })
}

promise中return的问题

一般情况下我们只考虑一种参数状态处理的时候就不需要return,比如上面的代码块,如果进入success时,success的回调函数就只有一局resolve,而且这是最后一句话,执行完就resolve出去了,就不用进行return操作,但是如果回调的时候考虑返回值的多种状态就需要进行return,否则resolve后面的函数仍会执行

function AA(a){
    return new Promise((resolve,reject)=>{
        if(a==3){
            console.log('resolve前面的语句')
            resolve('resolve的结果')
            console.log('resolve后面的语句')
        }
        else{
            reject
        }
    })
}
AA(3).then(res=>{console.log(res)})
//=>resolve前面的语句
//=>resolve后面的语句
//=>resolve的结果

return在起到中断返回的作用,但是是promise中then()的参数只能接受由resolve出去的,return的参数是无效的

function AA(a){
    return new Promise((resolve,reject)=>{
        if(a==3){
            return '3'
            resolve('resolve的结果')
        }
        else{
            reject
        }
    })
}
AA(3).then(res=>{console.log(res)})
//=>  无反应

then的使用

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

function AA(a){
    return new Promise((resolve,reject)=>{
        if(a==3){
            resolve('resolve的结果')
        }
    })
}
AA(3).then(res1=>{return AA(3)})
.then(res2=>{console.log(res2)})
//=> resolve的结果

只要在then中return出去的都会被包装为新的Promise实例,无论你return出去的是什么东西,并且return的值不可能return到promise外面被其他变量接收(解决办法:1.callback 2.async swait)

function AA(a){
    return new Promise((resolve,reject)=>{
        if(a==3){
            resolve('resolve的结果')
        }
    })
}
AA(3).then(res=>{return '3'})
.then(res=>{console.log(res)})
//=> 3

后续链式的.then只能接受resolve到的东西,而不会一个new Promise函数

function AA(a){
    return new Promise((resolve,reject)=>{
        if(a==3){
            resolve('resolve的结果')
        }
    })
}
AA(3)
.then(res=>{return AA(4)})
.then(res=>{console.log(res)})
.catch(res=>{console.log('catch ' + res)})
//=>catch 4

catch的使用

catch用于捕获reject从而达到rejected状态

.catch实际上是.then里面的第二个参数

function AA(a){
    return new Promise((resolve,reject)=>{
        if(a==3){
            resolve('resolve的结果')
        }
    })
}
AA(3)
.then(res=>{return Promise.reject('在catch捕获')})
.then(res=>{console.log('then' + res)},err=>{console.log('第二个参数位置' + err)})
//=>第二个参数位置在catch捕获

若不放回调里面,cathc无法捕获是哪个位置发生的异常

function AA(a){
    return new Promise((resolve,reject)=>{
        if(a==3){
            resolve('resolve的结果')
        }
        reject(4)
    })
}
AA(3)
.then(res=>{return AA(4)})
.then(res=>{console.log(res)})
.catch(res=>{console.log(res)}) 
.catch(res=>{console.log(res)})

如果我们想要第二个catch捕获,不用async写的话只能写在回调函数里面,这是promise的缺点(轻微回调地狱)

function AA(a){
    return new Promise((resolve,reject)=>{
        if(a==3){
            resolve('resolve的结果')
        }
        reject(4)
    })
}
AA(3)
.then(res=>{
        AA(4)
        .then(res=>{
            console.log(res)
        })
        .catch(res=>{
            console.log(res)
        })
    })
.catch(res=>{console.log(res)})

如果涉及多种catch判别,回调现象就很严重了


distance(this.hazardInfo.hazardLatitude, this.hazardInfo.hazardLongitude)//定位打卡
                .then(res => {
                    uploadMedia(this)//上传媒体
                        .then(res => {
                            // console.log(res);
                            this.$http.post('URL', this.hazardInfo) //上传表单
                                .then(res => {
                                    console.log(res);
                                    uni.showToast({
                                        icon: 'none',
                                        title: '上传成功',
                                        success() {
                                            setTimeout(() => {
                                                uni.navigateBack({
                                                    delta: 1
                                                });
                                            }, 2000);
                                        }
                                    });
                                })
                                .catch(err => {
                                    console.log(err);
                                    uni.showToast({ icon: 'none', title: '表单上传失败' });
                                });
                        })
                        .catch(() => {
                            uni.showToast({ icon: 'none', title: '媒体上传失败' });
                        });
                })
                .catch(type => {
                    switch (type) {
                        case 0:
                            uni.hideLoading();
                            uni.showToast({
                                icon: 'none',
                                title: '当前未在距离范围,请调整你的位置!',
                                position: 'bottom'
                            });
                            break;
                        case 1:
                            uni.hideLoading();
                            uni.showToast({
                                icon: 'none',
                                title: '定位失败,请开始定位权限',
                                position: 'bottom'
                            });
                        default:
                            break;
                    }
                });
        }

如果写成链式调用,你会发现永远是第一个catch捕获,你可能会说,我就在一个catch里面进行res的类型判断不就行了,那么我问你,如果涉及到多个AJAX的promise错误,500的错误都是一样的,你怎么进行类别的判断。如果是你自己封装的promise到可以,如果是用axios这种第三方库是肯定不行的。

3. async/await

由上面可以总结Promise有两个缺点

3.1 async

async函数永远返回的是promise对象,对,这种机制很像promise中then返回出去的东西,他只能是一个promise对象,无论你返回的是什么

async function testAsy(){
  return 'hello world';
}
let result = testAsy(); 
console.log(result)
promise状态.png

那么意味着可以对result进行
then操作

async function testAsy(){
  return 'hello world';
}
let result = testAsy(); 
result.then(res=>{console.log(res)})
//=>  hello world

catch操作

async function testAsy(){
  return Promise.reject('error')
}
let result = testAsy(); 
result
.then(res=>{console.log(res)})
.catch(res=>{console.log(res)})
//=>error

所以当没有await语句执行async函数,它就会立即执行,返回一个Promise对象,非阻塞,与普通的Promise对象函数一致

3.2 await

await如果等待的是Promise对象,则返回Promise的处理结果;如果是其他值,则返回该值本身await并不会进行等待。并且await会暂停当前async function的执行,等待Promise的处理完成。
await可以把primise返回的值给指定变量这是Promise语法无法实现的

如果await等的是Promise
function testAsy(x){
   return new Promise(resolve=>{setTimeout(() => {
       resolve(x);
     }, 3000)
    }
   )
}
async function testAwt(){    
  let result =  await testAsy('hello world');
  console.log(result);    
  console.log('tangj')    
}
testAwt();
console.log('tangSir')  
//=>tangSir
//=>3秒钟之后出现hello world
//=>tangj
如果await等的是其他值

await等待是是其他的值,那么个同步任务没有区别

function testAsy(x){
   return new Promise(resolve=>{setTimeout(() => {
       resolve(x);
     }, 3000)
    }
   )
}
async function testAwt(){    
  let result =  await 'no Promise';
  console.log(result);
  let res =  await testAsy('promise');
  console.log(result);    // 3秒钟之后出现hello world
  console.log(res )
  console.log('tangj')   // 3秒钟之后出现tangj
}
testAwt();
console.log('tangSir')  //立即输出tangSir
//=>tangSir
//=>no Promised
//=>3秒钟之后出现no Promise
//=>promise
//=>tangj

await缺点:是只能接受resolve的结果,无法处理catch的结果,但是会对reject进行报错,从而影响async函数的继续执行

function testAsy(x) {
    return new Promise((resolve,reject) => {
        if (x == 1) {
            resolve('1')
            return 
        }
        reject(2)
    })
}

async function testAwt(){    
   let res  = await testAsy(2)
    console.log(res  )
    console.log(3)
}
testAwt()
报错 不会打印下面的res和3.png

如果不想影响后面异步的进行(比如进入web首页,多页面迸发ajax,不能让其中一个错误的AJAX影响后面的AJAX请求),以下有两种操作

function testAsy(x) {
    return new Promise((resolve,reject) => {
        if (x == 1) {
            resolve('1')
            return 
        }
        reject(2)
    })
}

async function testAwt(){    
   let res  = await testAsy(2).catch(err=>{console.log(err)})
    console.log(res  )
    console.log(3)
}
testAwt()
//=>  2
//=> undefined
//=> 3

那么我上面的promise嵌套回调就可以进行下面的写法

async start(){
    let location = await distance(this.hazardInfo.hazardLatitude, this.hazardInfo.hazardLongitude)

     let meida = await uploadMedia()
     let form = await this.$http.post('URL', this.hazardInfo)
}
记得把原来的各种catch处理放到封装的promise函数的reject之前就行了

如果我们想既要catch异常,又不想继续往下执行,有两种办法

let { data } = await this.$http.post('/api', { username: username, password: password }}).catch(err => {
                this.isRotate = false;
            });
console.log(data)
console.log('1')
//=>TypeError: undefined is not an object (evaluating '_ref.data')  无法解构导致报错,无法往下进行
let  data  = await this.$http.post('/api, { username: this.username, password: this.password }}).catch(err => {
                this.isRotate = false;
            });
console.log(data)
console.log('1')
if(data == undefined )  return 
console.log('2')
//=>undefined
//=>1
上一篇下一篇

猜你喜欢

热点阅读