JavaScript中的async/await

基本概念

async包起来的代码块(或者函数)代表执行这块代码的时候,是异步执行的,也就是会先执行这个代码块下面的代码,然后再慢慢执行这个代码块里面的代码。(有点类似java中开启新Thread执行Runnable,async中的代码就是Runnable中的代码)

await用于执行async函数,并且await也只能在async代码块中使用。用await执行async函数,代表在整个async代码块中,会等待await后面的async函数异步执行得到结果后,才继续往下执行await下面的代码。也正是因为这个等待的特性,所以await也只能在async的异步代码块中使用。

在Parse中创建如下两个云函数,调用后会发现先得到了输出,也就是执行了return,然后2s后控制台打印出了log。

Parse.Cloud.define('nnn', async function(req) {
  (async ()=>{
    setTimeout(() => {
      console.log('log');
    }, 2000);
  })();
  return 'return';
});
Parse.Cloud.define('mmm', async function(req) {
  (async ()=>{
    console.log(await mmm2());
  })();
  return 'return';
});

function mmm2() {
  return new Promise(((resolve, reject) => {
    setTimeout(() => {
      resolve('log');
    }, 2000);
  }))
}

async函数本身return promise

1. 没有显式return,相当于return Promise.resolve(undefined);

2. return非Promise的数据data,相当于return Promise.resolve(data);

3. return Promise, 会得到Promise对象本身

async函数内部若需要做异步的事情,比如定时器结束之后异步返回结果,需要在这个async内部通过return new Promise((resolve, reject)=>{})返回Promise,并在得到结果后resolve,参考0002()。但是如果异步的事情也是一个async函数,也就是说async函数内通过await调用其他async函数,可以直接return,参考000()。

// 延时2s返回log
Parse.Cloud.define('ooo', async function(req) {
  return await ooo();
});

async function ooo() {
  return await ooo2();
}

async function ooo2() {
  return new Promise(((resolve, reject) => {
    setTimeout(() => {
      resolve('resolve');
      // reject('reject');
    }, 2000);
  }))
}

如果调用async函数时不加await,则只相当于在调用的地方插入了async代码块异步执行,并不会等待结果再继续执行。同时因为没有通过await调用意味着该async函数中的异常无法被外部捕获,如果抛出异常将会引发UnhandledPromiseRejectionWarning报错。

// async的函数不通过await调用,会直接输出return,2s后打印log
Parse.Cloud.define('rrr', async function(req) {
    rrr();
    return 'return';
});

async function rrr() {
    return new Promise(((resolve, reject) => {
        setTimeout(() => {
            console.log('log');
            resolve();
        }, 2000);
    }))
}

利用这个特性,可以在需要异步调用某个函数并且并不需要这个函数返回值的时候,将这个函数声明为async,并在调用的地方不加await。需要注意的地方是该函数内部必须捕获异常,否则外部调用时没有await是无法捕获异常的。

异常处理

throw必须被层层捕获,如果某一层没有捕获,就会出现UnhandledPromiseRejectionWarning报错。

在Promise中可以直接使用throw error来抛出异常,效果跟reject(error)一致。在async函数内也可以直接使用throw来抛出异常。reject和throw都会被上一层的调用者捕获。

如果只使用非代码块的async函数和await,不需要层层手动捕获异常,会自动上抛。如下代码无需手动try-catch,最终抛出throw。

Parse.Cloud.define('ppp', async function(req) {
  return await ppp();
});

async function ppp() {
  return await ppp2();
}

async function ppp2() {
  throw 'throw';
}

如果是async代码块,throw的异常并不会被捕获,只能在async代码块内部try-catch自行捕获。如果要将异常再次抛给外部,需要在外部包一层Promise,并且使用Promise的reject来抛出异常。

async代码块内部的异常无法通过外部的try-catch捕获,就像java中创建新Thread执行Runnable时无法通过Thread外部的try-catch捕获Runnable中的异常一样。

没有处理async中的异常:

new Promise((resolve, reject) =>{
    (async()=>{})()
})

捕获异常成功:

new Promise((resolve, reject) =>{
    (async()=>{
        try{
            ...
            resolve()
        } catch(e){
            reject(e)
        }
    })()
})

捕获失败:

new Promise((resolve, reject) =>{
    try{
        (async()=>{
            // 这里的异常无法在async外部被捕获,所以这样做会报错
            throw ''
        })()
    } catch(e) {
        reject(e)
    }
})

Promise中通过await调用的其他async函数,也必须去捕获它的异常,并且放进reject,否则会出现UnhandledPromiseRejectionWarning报错。

便利性

从使用回调式改为使用async/await形式的Promise之后,最明显的改观是代码量变少了,不再有回调地狱。当然相应的,在还没有完全习惯之前,写代码或者看代码出错的概率会相应提高。

代码调试上,不再需要像之前那样在回调中设置断点,只需要单步到底,因为IDE的单步调试能完美适应async/await。

递归上,之前的回调式中,要实现递归,需要写两个函数,通过Callback调用实现递归,代码可读性比较低,每次要写递归的时候还需要想一下。现在使用了async/await之后,甚至在原来需要用递归的地方,只需要使用循环就能搞定。

Share

You may also like...

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注