#Promise async/await总结

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

  • 定时器settimeout
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
  • 事件绑定addEventListener,onclick
for (var i = 0; i < tabList.length; i++) {
    //tabList[i] <=>每一轮循环当前要操作的LI DOM对象
    tabList[i].onclick = function () {
        alert(i);
        changeTab(i);//=>需要把当前点击的这个LI的索引传递进来
    }
}*/
当绑定完毕事件后,进行点击发现i全都是3了
  • AJAX一般采取异步处理
  • promise
  • async/await

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基本概述
  • promise的三种状态pending,fulfilled,rejected,状态只能是3个中的一种
  • 状态改变结果就定死了,不能再改变状态
  • resolve,reject其实就是你在声明Promise中的一道往外传递参数的.then()中的参数就是你从这个门往外resolve的参数,.cathch()中的参数就是你从这个门往外reject的参数
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有两个缺点

  • 涉及多种catch类型捕获,需要写到回调函数里面,如果链式写法会永远被第一个catch捕获
  • then里面的值无法return出去,只能被下一个then进行操作

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请求),以下有两种操作

  • try catch
  • .catch
    下面我只对.catch进行说明,try catch在我看来反而还不如promise美观
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异常,又不想继续往下执行,有两种办法

  • 解构赋值,适用于promise返回的为对象
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')  无法解构导致报错,无法往下进行
  • 用普通变量接promise返回值,适用于任何类型的返回值
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

3.3注意事项

如果想用async去封装Promise函数的话,注意内部的其他函数必须是能够返回promise的,如果不是返回Promise的实际上它还会是异步

正确的封装
/**
 * @description 查询数据
 * @param {*} tableName 表名
 * @param {*} where 查询条件
 */
exist(tableName, where) {
    return new Promise((resolve, reject) => {
        var sql = `select * From ${tableName} where ${where}`
        plus.sqlite.selectSql({
            name: 'DataCollectorDB',
            sql: sql,
            success(e) {
                console.log(`查询${tableName}表数据成功:${JSON.stringify(e)}`);
                resolve(e)
            },
            fail(e) {
                console.log(`查询SQL语句失败:${JSON.stringify(e)}`);
                reject(`查询SQL语句失败:${JSON.stringify(e)}`)
            }
        })
    })
}
错误的封装
/**
 * @description 查询数据
 * @param {*} tableName 表名
 * @param {*} where 查询条件
 */
async exist(tableName, where) {
    var sql = `select * From ${tableName} where ${where}`
    plus.sqlite.selectSql({
        name: 'DataCollectorDB',
        sql: sql,
        success(e) {
            console.log(`查询${tableName}表数据成功:${JSON.stringify(e)}`);
            return
        },
        fail(e) {
            console.log(`查询SQL语句失败:${JSON.stringify(e)}`);
            return Promise.reject(`查询SQL语句失败:${JSON.stringify(e)}`)
        }
    })
}
正确的封装
/**
 * @description 查询数据
 * @param {*} tableName 表名
 * @param {*} where 查询条件
 */
async exist(tableName, where) {
    var sql = `select * From ${tableName} where ${where}`
    let res = await plus.sqlite.selectSql({...})//假如这个函数已经封装过Promise
}

上面的对比可以看出来,plus.sqlite.selectSql本身就是一个异步的函数,并且它没有被官方封装为Promise,如果我们用async去标识它,那么实际上他仍然是一个异步的函数,即使你用await去等也是,此时实际上在末尾的位置有一句return undefined,所以你在外部是拿不到返回的结果的,这种情况下你只能用Promise模式去封装,它立刻执行包装在里面的函数,然后用resolve去等结果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,706评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,002评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,462评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,375评论 0 216
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,763评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,849评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,033评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,768评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,490评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,734评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,204评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,566评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,227评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,137评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,934评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,926评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,774评论 2 274

推荐阅读更多精彩内容