async/await的深入总结

---async和await是干什么用的---

  • 一句话:是用来解决异步流程问题的。

  • 避免回调地狱(callback hell),且以最接近同步代码的方式编写异步代码

  • async/await是对co模块的升级,内置生成器函数的执行器,不再依赖co模块。

  • 不了解promise的请点击

  • generator的相关文章请点击

---语义---

async 可以声明一个异步函数,此函数需要返回一个 Promise 对象。await可以等待一个 Promise 对象 resolve,并拿到结果。

function aaa(){
      return new Promise((resolve) => {//返回一个promise对象
          setTimeout(function(){//这里使用计时器模拟异步,也可以使用ajax,node异步读写等。。
              resolve(123);  //异步执行resolve函数
          },2000)
      })
  }
  async function getaaa(){
      let a=await aaa();//等待aaa函数的执行结果
      console.log(a)//拿到执行的返回值
  }
  getaaa()//调用这个函数

---小试牛刀---

  • 一般用法(模拟同步写法)
var sleep = function (time) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve();//也可以把resolve写在sjax的sussess的回调中,都是异步调取
        }, time);
    })
};
var start = async function () {
    // 在这里使用起来就像同步代码那样直观
    console.log('start');//立即打印start
    await sleep(3000);//等待三秒
    console.log('end');//三秒后打印end  
};

start();
  • async函数捕捉异常(也可以用promise对象链上的catch方法)
var sleep = function (time) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            // 模拟出错了,返回 ‘error’
            reject('error');
        }, time);
    })
};

var start = async function () {
    try {
        console.log('start');
        await sleep(3000); // 这里得到了一个返回错误
        // 所以以下代码不会被执行了,会走catch
        console.log('end');
    } catch (err) {
        console.log(err); // 这里捕捉到错误 `error`
    }
};
  • 并行(依赖于Promise.all方法)
let count = ()=>{
    return new Promise((resolve,reject) => {
        setTimeout(()=>{
            resolve(100);
        },500);
    });
}

let list = ()=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve([1,2,3]);
        },500);
    });
}
let getList = async ()=>{
    let result = await Promise.all([count(),list()]);
    return result;
}

//调用
getList().then(result=> {
    console.timeEnd('begin');  //begin: 505.557ms后才执行
    console.log(result);       //[ 100, [ 1, 2, 3 ] ]
}).catch(err=> {
    console.timeEnd('begin');
    console.log(err);
});

---关于async 函数---

async 函数就是 Generator 函数的语法糖。
对比generator : async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

const fs=require('fs');
function readFile(url){
    return new Promise(function (resolve, reject) {
        fs.readFile(url,'utf-8', function (err, data) {
            if(err)reject(err);
            resolve(data);
        })
    })
}
function* read(){//generation函数
    const f1=yield readFile('../17.promise原理/201808-promise/1.txt');
    const f2=yield readFile('../17.promise原理/201808-promise/2.txt');
    console.log(f1,'Generator');
    console.log(f2,'Generator');
}

//async
async function read2(){
    const f1=await readFile('../17.promise原理/201808-promise/1.txt');
    const f2=await readFile('../17.promise原理/201808-promise/2.txt');
    console.log(f1,'async');
    console.log(f2,'async');
}
read2()

async函数对 Generator 函数的改进

  1. Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器,async函数的执行,与普通函数一模一样。
  2. 更好的语义
  3. co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
  4. 返回值是 Promise,比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作
    async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
  • async 函数有多种使用形式。(async后面必须紧跟着函数)
// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭头函数
const foo = async () => {};

错误处理机制

  • 产生错误的情况:async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态,会阻断后续代码的执行。
async function fn(){
 throw new Error('err')//抛出错误,阻断代码执行
 console.log('上边抛出错误')
}
fn()

async function fn2(){
    await new Promise(function (resolve, reject) {//抛出错误,阻断代码执行
        reject('err2');
    })
  console.log('上边抛出错误')
}
fn2()
  • async处理错误有两种方式
//处理错误最常用的方式try catch
async function fn(){
    try{
        await new Promise(function (resolve, reject) {
            reject('err')
        })
    }catch(err){
        console.log('上边抛出错误'+err)
    }

}
fn()
//用catch捕捉错误(因为async返回的是一个promise)
//内部捕捉
async function fn2(){
    await new Promise(function (resolve, reject) {
        reject('err2')
    }).catch(function (err) {
            console.log('上边抛出错误'+err)
        })
    console.log('这接着执行')
}
fn2()
//外部捕捉
async function fn3(){
    await new Promise(function (resolve, reject) {
        reject('err3')
    })
    console.log('这就不执行了')
}
fn3().catch(function (err) {
    console.log('上边抛出错误'+err)
})

await 命令

  1. 正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。个人认为:只有后面接一个Promise对象或thenable时await才会有意义
async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123  then中的回调都是异步的(来源于promiseA+规范)
  1. await命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象
//回想一下手写封装的promise原理中的resolvePromsie对象,判断的就是thenable时,递归判断
//任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}

使用注意点

  1. 前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
  2. 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
//独立的异步操作(不相互依赖的情况)
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();

let foo = await fooPromise;
let bar = await barPromise;
//以上两种都是并发的方式,效果一样
  1. 异步函数之间相互依赖,需要继发执行
// 写法一
async function main() {
    var foostr = await getFoo();
    var barstr = await getBar();
    console.log(foostr )
    console.log(barstr);
}
main()
// 写法二
async function main2() {
    let docs = [getFoo, getBar];
    for (let doc of docs) {
        var str = await doc();
        console.log(str);
    }
}
main2()

//对比并发和继发执行的不同
  1. await命令只能用在async函数之中,如果用在普通函数,就会报错
  2. async 函数可以保留运行堆栈。(不太清楚用途)



---实现原理---

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

async function fn(args) {
  // ...
}

// 等同于

function fn(args) {
//spawn这个自动执行器,执行一个generation
  return spawn(function* () {
    // ...
  });
}

//自动执行器

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

关于执行顺序的问题

async function async1 () {
  console.log('async1 start')
  await async2();
  console.log('async1 end')
}
 
async function async2 () {
  console.log('async2')
}
 
console.log('script start')
 
setTimeout(function () {
  console.log('setTimeout')
}, 0)
 
async1();
 
new Promise (function (resolve) {
  console.log('promise1')
  resolve();
}).then (function () {
  console.log('promise2')
})
 
console.log('script end')

关于执行顺序的注意事项:

  代码执行顺序,从上到下,从左到右,同步>>异步,微任务>>宏任务
  then的回调属于微任务
  计时器等异步属于宏任务
  await下边的代码都是异步执行的
  return await 123  等价  return 123 ,下边的代码不执行
  promise的状态不改变,then中的回调不执行

---对比Promise,co,async/await的区别---

  • 1.promise
    链式调用,避免回调地狱(连环嵌套)
    每次then拿取返回值不能跨then拿取数据,只能拿到当前的上一个传下来的
    new Promise(...).then(res1=>{}).then(res2=>{}).then(res3=>{这里想要用res1需要一步一步传下来才行})

  • 2.co
    需要引用插件,依赖generator

  • 3.async/await()----最好用!!!!
    ES7 中更加标准的解决方案(需要babel)。
    语法简洁,代码优雅
    每次都能拿到异步函数的返回值,避免嵌套

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

推荐阅读更多精彩内容

  • 异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本...
    呼呼哥阅读 7,254评论 5 22
  • async 函数 含义 ES2017 标准引入了 async 函数,使得异步操作变得更加方便。 async 函数是...
    huilegezai阅读 1,243评论 0 6
  • 本文首发在个人博客:http://muyunyun.cn/posts/7b9fdc87/ 提到 Node.js, ...
    牧云云阅读 1,658评论 0 3
  • 简单介绍下这几个的关系为方便起见 用以下代码为例简单介绍下这几个东西的关系, async 在函数声明前使用asyn...
    _我和你一样阅读 21,103评论 1 24
  • 今天是我们“一起晒早餐”的第9天,总共收集了76份早餐食谱。 之前有小伙伴问到,早餐要怎么搭配这个话题。今天咱们就...
    维他哥小食姐阅读 487评论 2 7