JS异步编程--promise、async/await

630 阅读8分钟

异步定义

任务A执行到一半,暂停,去执行另外一段任务B,完了再回到断点处继续执行A,这是异步编程最直观的理解。

假定一个场景

  • JS读取文件a.txt,获取到数据dataA=1;
  • 再读取文件b.txt,获取到数据dataB=2;
  • 再读取文件c.txt,获取到数据dataC=3;
  • 求和 sum = 6。

旧的回调函数解决异步操作

function successCallback(result){
    cosnole.log('成功' + result)
}
function failureCallback(error){
    console.log('失败' + error)
}
createAudioFileAsync(set,successCallback,failureCallback)

promise

定义:Promise是ES6中提供异步编程的解决方案,可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

具体表达:

  • 语法上:Promise是一个构造函数
  • 功能上:Promise对象用来封装一个异步操作并可以获取其结果

promise流程

promise解决了哪些开发中的痛点

  • 回调地狱,代码难以维护,第一个的函数的输出是第二个函数的输入这种现象,可读性差
doSomething(function(result){
    doSomethingElse(result,function(newResult){
       doThirdThing(newResult,function(finalResult){
          console.log('得到数据')
       },failureCallback)
    },failureCallback)
},failureCallback)
  • promise可以支持多个并发的请求,获取并发请求中的数据

  • promise可以解决异步的嵌套带来的可读性的问题

doSomething().then(function(result){
   return doSomethingElse(result)
})
.then(function(newResult){
    return doThirdThing(newResult)
}).then(function(finalResult){

   console.log('数据')
}).catch(failureCallBack)
  • promise 更加灵活,旧的回调函数,必须在启动异步任务前指定。promise:启动异步任务 =>返回promise对象=>给promise对象绑定回调函数

如何使用promise

  • 创造一个Promise实例
  • Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数
  • 可用Promise的try和catch方法预防异常
// 创建一个新的Promise对象
const p = new Promise((resolve,reject) =>{     //执行期函数

// 执行异步操作任务
setTimeout(() => {
    const time = Date.now()
    if(time %2 == 0 ){
     resolve('成功的数据')
    }else{
     reject('失败的数据')
     }
  },1000)
})

p.then(
    value =>{      //接收得到成功的value数据
       console.log('成功')
    }
    reason =>{     // 接收得到失败的reason数据
       console.log('失败')
    }
)

promise特点:

1.对象的状态不受外界影响:只有异步操作的结果才能修改内部状态。 Promise共有三种状态。 Pending(进行中),Fulfilled(成功),Rejected(已失败)。

2.一旦状态改变,就不会再变更: 状态变更 Pending =>Resolved 或者 Pending => Rejected。 成功的结果数据一般称为value,失败的结果数据一般称为reason

Promise缺陷:

1:一旦新建,就会立即执行,无法取消。

2:如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。不能使用 try catch 来捕捉异常。

3: 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

4:同async 用法相比,大量then的使用,代码较为繁琐。

Promise基本用法:

Promise是一个对象,生成Promise实例,构造函数接收一个函数作为参数。Promise实例使用then来处理回调。then可以接收两个参数,第一个是成功后的回调,第二个是失败时的回调(非必填)。

1:创建后会立即执行 2:Promise 的链式调用。 使用 then 和 catch 方法实现链式调用。虽然then 的入参有两个,但是失败状态不建议在then内部实现,因为如果是链式调用的话,会造成代码繁琐,采用 catch方法,可简化实现方式。 采用统一抛出异常方式 ,在链式调用后catch方法内捕捉。避免每个then,都去实现 reject的回调。

Promise方法:

  • Promise.then

    • .then 它自己的执行结果是一个 promise。这意味着我们可以链接任意数量的 .then,前一个 then 回调的结果将会作为参数传递给下一个 then 回调.
  • Promise.all

    • 将多个Primise 实例合并成一个实例,只有当所有实例都变成resolved状态,或者其中一个变成rejected状态,才会执行回调。 Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个[Promise]实例, 那个输入的所有 promise 的 resolve 回调的结果是一个数组。这个[Promise]的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。
    • 它的 reject 回调执行是,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且reject的是第一个抛出的错误信息。
  • Promise.race()

    • 是将多个Promise实例,包装成一个新的Promise实例,多个实例有一个实例状态改变,实例状态就改变了,返回的第一个改变的实例状态。
  • done()

    • Promise对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
  • finally()

    • finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。实例可以用来网络请求中的loading

promise.then()返回的新promise的结果状态由什么决定

1)简单表达:由then()指定的回调函数执行的结果决定.

2)详细表达:

  • 如果抛出异常,新promise变为rejected,reason为抛出的异常
  • 如果返回的是非promise的任意值,新promise变为resolved,value为返回的值
  • 如果返回的是另一个新promise,此promise的结果就会成为新的promise的结果
new Promise((resolve,reject)=>{
    resolve(1)
}).then(
    value => {
    console.log('onResolved()',value) //  1
 },
 reason => {
  console.log('onRejected()',reason)
 }
).then(
 value =>{
  console.log('onResolved()',value) // undefined
 },
 reason => {
  console.log('onRejected()',reason)
 }
)

总结:1 undefined

promise异常传透?

  • 当使用promise的then链式调用时,可以在最后指定失败的回调
  • 前面任何操作除了异常,都会传到最后失败的回调中处理

中断promise链

  • 当使用promise的then链式调用时,在中间中断,不再调用后面的回调函数
  • 办法:在回调函数中返回一个pending状态的promise对象

async / await (用同步方式,异步执行操作)

async---定义异步函数(async function someName(){...})

  • async 位于函数字面量或函数表达式的前面(普通函数、立即执行函数和箭头函数均可),被修饰函数执行后会返回一个 Promise 对象。

  • 函数体内返回值则作为 resolve 的参数。

  • 函数体内抛出错误,则错误信息作为 reject 的参数。

async function test() {
    console.log("test");
    return 20;
}

// 等价于
function test() {
    return new Promise((resolve, reject) => {
        console.log("test");
        resolve(20);
    });
}

await--- 暂停异步函数的执行 (var result = await someAsyncCall();)

  • await 一定要位于 async 函数内部。

  • await 一般位于 Promise 对象之前,所以一般位于 async 函数执行的前面,但若是返回值为 Promise 对象的普通函数也可。

  • await 会拿到该对象的结果,也就是 then 中 resolve 或 reject 的参数。如果不是 Promise 对象,则直接返回对应的值。

  • 若 await 后方不是 Promise 对象,则会将其用 Promise.resolve 包装后执行。

  • await 的执行会被强制等待至拿到结果,后续函数体内的代码执行被阻塞,函数本身不会阻塞整体代码

// 定义一个返回Promise对象的函数
function fn() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(30);
        }, 1000);
    })
}

// 然后利用async/await来完成代码
const foo = async () => {
    const t = await fn();
    console.log(t);
    console.log('next code');
}

foo();

// result:
// 30
// next code

解析:

当在async函数中,运行遇到await时,就会等待await后面的函数运行完毕,而不会直接执行后面的next code。

async和await的作用

  • 简化 Promise 的使用过程。
  • 让你的异步代码看起来像是同步的
  • async和await是用来处理异步的。即你需要异步像同步一样执行,需要异步返回结果之后,再往下依据结果继续执行。
  • async 是“异步”的简写,而 await 可以认为是 async wait 的简写。
  • async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

异常处理

在Promise中,我们知道是通过catch的方式来捕获异常。而当我们使用async时,则通过try/catch来捕获异常。

// promise 写法
function fn() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('some error.');
        }, 1000);
    })
}

// async写法
const foo = async () => {
    try {
        await fn();
    } catch (e) {
        console.log(e);  // some error
    }
}

foo();

如果有多个await函数,那么只会返回第一个捕获到的异常。

function fn1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('some error fn1.');
        }, 1000);
    })
}
function fn2() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('some error fn2.');
        }, 1000);
    })
}

const foo = async () => {
    try {
        await fn1();
        await fn2();
    } catch (e) {
        console.log(e);  // some error fn1.
    }
}

foo();

额外补充

如果promise发生异常,则会进到then第二个参数的回调函数,如果这个参数不存在,则进到catch中。

let p = new Promise((resolve, reject) => { 
      throw new Error('some errors')
      // resolve()
      // reject()
    }); 
    p.then(res => { 
      console.log('1111') 
    },res => {
      console.log('2222')
    }).catch(e => {
      console.log('3333')
    })
    /**
     * 结果为:
     * 2222
    */

在一个失败操作(即一个 catch)之后可以继续使用链式操作,即使链式中的一个动作失败之后还能有助于新的动作继续完成。

new Promise((resolve, reject) => {
    console.log('Initial');
 
    resolve();
})
.then(() => {
    throw new Error('Something failed');
        
    console.log('Do this');
})
.catch(() => {
    console.log('Do that');
})
.then(() => {
    console.log('Do this whatever happened before');
})

输出结果: Initial

Do that

Do this whatever happened before