ES6——async函数

文章目录

一、概念

async函数:处理异步编程 类似状态机; 封装多个promise进行异步编程

也就是 Generator 函数的语法糖。

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

将 Generator 函数的星号(*)替换成async,将yield替换成await:

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

特点:

  1. async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结
  2. async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)
  3. async函数的返回值是 Promise 对象
  4. async函数内部return语句返回的值,会成为then方法回调函数的参数

针对3、4:

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到

async function f() {
  throw new Error('出错了');
}

f().then(
  v => console.log('resolve', v),
  e => console.log('reject', e)
)
//reject Error: 出错了

二、对比 Promise联用Generator && Promise联用async

1.Promise联用Generator

    let fun= function () {
        return new Promise(function (resolve,reject) {
            setTimeout(function () {
                resolve("数据");
            },2000)
        })
    }
    
    let f= function* () {
        yield fun();
    }
    let inter=f();
    //console.log(inter.next()); //{value: Promise, done: false}
    inter.next().value.then(function (res) {
        console.log(res);   //两秒后输出: 数据
    });

ES6——async函数

2.Promise联用async

async函数返回一个 Promise 对象

async函数内部return语句返回的值,会成为then方法回调函数的参数

    let fun= function () {
        return new Promise(function (resolve,reject) {
            setTimeout(function () {
                resolve("数据");
            },2000)
        })
    }

    //async函数 返回也是promise对象 then catch finally
    let asy=async function () {
        let result=await fun();
        return result;
    }
    asy().then(function (res) {
        console.log(res);  //两秒后输出: 数据
    })

3.async处理多个Promise(等待间无关)

    let fun1= function () {
        return new Promise(function (resolve,reject) {
            setTimeout(function () {
                resolve("数据");
            },2000)
        })
    }
    let fun2= function () {
        return new Promise(function (resolve,reject) {
            setTimeout(function () {
                resolve("成功");
            },3000)
        })
    }

    let asy=async function () {
        let res_1=await fun1();
        let res_2=await fun2();
        return{
            res_1,res_2
        }
    }
    asy().then(function (res) {
        console.log(res);
    })

总共等待五秒(两者之和)后输出:
ES6——async函数

4.async处理多个Promise(等待间有关)

    let method=async function(){
        let p1=await new Promise(function (resolve) {
            resolve(true);
        })
        let result=await (function (args) {
            return new Promise(function (resolve) {
                if(args){
                    resolve("成功");
                }
                else{
                    resolve({result:'error'});
                }
            })
        })(p1);
        return result;
    }
    method().then(function (res) {
        console.log(res);  //成功
    })

三、Promise 对象的状态变化

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变(除非遇到return语句或者抛出错误)
即只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数(例如上面的第3个例子)

四、await 命令

1.正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。

2.如果不是 Promise 对象,就直接返回对应的值

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123

3.实例不是 Promise 对象,但是因为定义了then方法,await会将其视为Promise处理

4.await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到

async function f() {
  await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了

上面代码中,await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。这里如果在await前面加上return,效果是一样的。

5.任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}

上面代码中,第二个await语句是不会执行的,因为第一个await语句状态变成了reject

6.有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。

(1)这时可以将第一个await放在try…catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

async function f() {
  try {
    await Promise.reject('出错了');
  } 
  catch(e) {  //不写,才会正常返回
  }
  return await Promise.resolve('hello world');
}

f().then(v => console.log(v))
// hello world

(2)另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误

async function f() {
  await Promise.reject('出错了').catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f().then(v => console.log(v))
// 出错了
// hello world

五、错误处理

如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject

    let fun=async function(){
        await new Promise(function (resolve,reject) {
            throw new Error('erroe');
        })
        return '123';
    }
    fun().then(function (res) {
        console.log(res);
    }).catch(function (err) {
        console.log(err);
    }) 
    //输出: Error: erroe

1.使用try…catch来防止出错

    let fun=async function(){
        try{
            await new Promise(function (resolve,reject) {
                throw new Error('erroe');
            })
        }
        catch(e){
//            throw e;  //不写可以防止抛出异常,正常返回值
        }
        return '123';
    }
    fun().then(function (res) {
        console.log(res);
    }).catch(function (err) {
        console.log(err);
    })
    //输出: 123

如果要捕获异常:

    let fun=async function(){
        try{
            await new Promise(function (resolve,reject) {
//                throw new Error('error');  //两种都可以
                reject("失败");
            })
        }
        catch(e){
            throw e;  //不写可以放置抛出异常,正常返回值
        }
        return '123';
    }
    fun().then(function (res) {
        console.log(res);
    }).catch(function (err) {
        console.log(err);
    })
    //输出: 失败  或 Error: error

2…如果有多个await命令,可以统一放在try…catch结构中

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}

六、async 函数注意点

1.把await命令放在try…catch代码块中

前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。

前面示范了两种写法

2.同时触发多个await (Promise.all)

多个await命令后面的异步操作,如果不存在继发关系,最好把它们整合成一个promise,让它们同时触发

let foo = await getFoo();
let bar = await getBar();

getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发

let [foo, bar] = await Promise.all([getFoo(), getBar()]);

还有一种方法:

let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

3.await命令只能用在async函数之中,如果用在普通函数,就会报错

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

上面代码会报错,因为await用在普通函数之中了。但是,如果将forEach方法的参数改成async函数,也有问题。

function dbFuc(db) { //这里不需要 async
  let docs = [{}, {}, {}];

  // 可能得到错误结果
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}

上面代码可能不会正常工作,原因是这时三个db.post()操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}
上一篇:leetcode104_Maximum Depth of Binary Tree


下一篇:[二叉树]leetcode104:二叉树的最大深度(easy)