async await promise try...catch

简单介绍下这几个的关系
为方便起见 用以下代码为例简单介绍下这几个东西的关系,

async function buildData(name) {
      try {
        let response1 = await axios.get('/api/user?name=' + name);
        let userInfo = response1.data;
        
        let response2 = await axios.get('/api/topics?user_id' + userInfo._id);
        let posts = response2.data;
        // i got it.
      } catch(err) {
        console.log(err);
      }  
    }
    
    buildData('xiaoming');

async

在函数声明前使用async关键词修饰 说明函数中有异步操作

await

等待 后面的代码执行完毕 再继续向下执行

promise

Promise 是一个对象,从它可以获取异步操作的消息,知道异步函数是完成了还是出错了。
axios返回的结果就是一个promise

try catch

try catch JavaScript的异常捕获机制,凡是在try语句块中的代码出错了,都会被catch捕获。

上面的代码就是说

  1. buildData 这个函数被 async 修饰 说函数中有异步操作
  2. await 等待异步操作结果
  3. 如果有错误发生 使用try catch 捕获异常

上面说的太过简单,简要的说明下 各个东西是干啥的。这里的核心是 promise 下面会逐个介绍

Promise


Promise字面上讲,是一个承诺。这个承诺有三个状态

  • pending 进行中(悬而未决)
  • fulfilled 已成功(以满足)
  • rejected 已失败(已拒绝)

从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点:

  1. 对象的状态不受外界的影响。只有异步操作的结果可以决定当前是哪种状态。
  2. 状态一旦改变 就不会再变,任何时候都可以得到这个结果。promise对象状态的改变只有两种情况,
    • pending 到 fulfilled
    • pending 到 rejected

只要这两种情况发生,状态就凝固了,不会再改变了,会一直保持这个结果,这时就定型了 resolved。
如果改变已经发生了,任何时候添加回调函数,得到的都是这个结果。

因为创建Promise对象时,回调函数中有resolve和reject两个参数,后续的resolved统一指的是fulfilled状态,不包括rejected状态

一个Promise对象一旦状态确定了,它的使命也就结束了。后面的代码都不应该再执行了,最好return resolve();
如果状态已经resolve了,再在后面抛出错误也是无效的,也不会改变状态为rejected,后面有异常也不会抛出。

Promise对象如何知道异步操作结果又如何传递


Promise对象如何知道异步操作的结果呢,那就是回调函数了,一个表示成功resolve,一个表示失败reject,

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
下面代码创造了一个Promise实例。

var promise = new Promise(function(resolve, reject) {
    // ... some code
    console.log(我一创建就执行了)
    if (/* 异步操作成功 */){
      resolve(value);
    } else {
      reject(error);
    }
  });

Promise新建后,就会立即执行,返回一个Promise对象。
Promise构造函数需要一个函数作为参数,这个函数有两个参数,分别是resolve和reject,
它们是两个函数,由 JavaScript 引擎提供,不用自己部署

  • resolve: 把Promise状态 从 pending 变为 resolved ,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
  • reject: 把Promise状态 从 pending 变为 reject ,在异步操作失败时调用,并把错误作为参数传递出去
    此外,reject方法的作用,也等同于抛出异常。reject的参数会被catch捕获。即便没有调用reject,如果执行过程中出错了,也会被catch捕获。

所以:

  1. 异步操作成功时,把结果告诉resolve回调函数 异步操作失败 把错误告诉reject回调函数
  2. resolve 和 reject 回调函数 还有一个作用就是把 结果传递出去

Promise对象如何使用异步函数的执行结果


通过then方法获取异步操作的结果。
Promise实例有两个方法:

  • then 指定了resolve状态和reject的回调函数
  • catch 专门用来捕获Promise对象产生的错误

then和catch方法返回的是一个新的Promise对象,因为Promise对象具有then和catch方法,所以可以一直.then和.catch下去
axios返回的也是Promise对象,这也是为什么axios可以那么链式操作了

then方法的作用就是干这个

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法的参数是两个回调函数,都接受Promise对象传出的值作为参数:

  • 第一个参数是resolve状态的回调函数
  • 第二个参数是reject的回调函数, 这个参数是可选的

通常这个参数也不写,因为Promise实例还有一个方法叫catch是专门用来捕获异常的。

catch


catch 是 .then(null, rejection)的别名,用于指定发生错误时的回调函数。

一旦catch前面的任何一个Promise发生异常,都会被catch捕获,包括Promise函数创建的Promise,还有.then返回的Promise,甚至catch前面如果还有一个catch在这个catch抛出的异常也会被后一个catch捕获。

也就是说:
Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止,也即是说,错误总会被下一个catch语句捕获。

所以,既然这个catch这么厉害,then函数中的第二个参数常常被省略了,然后被这个catch方法替代。

所以通常这么写:
promise.then().catch()
promise.then().then().catch()
promise.then().then().catch().then().catch()

所以下面例子第二种写法好些。

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法和catch方法。

Promise 还有两个常用方法:

  • Promise.all() :将多个Promise实例,包装成一个新的Promise实例。内部所有的Promise的状态都变成fulfilled,这个Promise状态才会变成fulfilled,返回值是一个数组,但是只要有一个 rejected 这个Promise对象就会变成rejected 返回第一个被reject的实例的返回值
  • Promise.race():跟all方法一样,只是race就想是赛跑,谁先有结果,返回谁的结果。不会等到所有的Promise都执行完。

现有对象转为Promise对象

Promise.resolve方法就起到这个作用

Promise.resolve('foo')
// 等价于
nnew Promise(function (resolve) {
    resolve('foo')
})

Promise.resolve方法的参数分成四种情况:

  • 参数是一个Promise实例 :不做任何修改、原封不动地返回这个实例。
  • 参数是一个thenable对象 :将这个对象转换为Promise对象,然后立刻执行thenable的then方法
  • 参数不是具有then方法的对象,或根本就不是对象 :反回一个新的Promise对象,状态为resolved。
  • 不带有任何参数: 直接返回一个resolved状态的Promise对象
    立即resolved的对象,是在本轮事件循环结束时,而不是在下一轮循环时间开始时
setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

上面代码中,setTimeout(fn,0),在下一轮循环事件开始执行
Promise.resolve()在本轮事件循环结束时执行
console.log('one')立刻执行,
因此上面的打印顺序是 one two three

直接返回一个状态为rejected Promise对象


Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

  var p = Promise.reject('出错了');
    //等价于
  new Promise(function (resolve,reject) {
      reject('出错了');
  })

Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。不会像Promise.resolve那样,根据不同的情况包装Promise

async和await


async函数是Generator函数的语法糖,将Generator的星号换成async 将yield换成await

Generator是一个状态机,封装了多个内部状态,执行 Generator 函数会返回一个遍历器对象,返回的遍历器对象,可以使用next依次遍历 Generator 函数内部的每一个状态。Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。Generator 函数的执行必须靠执行器。

async函数比Generator函数更好用

  • 自带执行器,执行起来,跟调用普通函数一样
  • async和await 语义更清晰,async表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果
  • await后面啥都可以跟,可以是Promise 也可以是对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
  • async函数的 返回值是 Promise

正常情况下,await命令是个Promise对象,如果不是 会被转成一个 立即 resolved的对象
async函数完全可以看作多个异步操作,包装成的一个 Promise 对象(因为await 函数返回的是Promise对象),而await命令就是内部then命令的语法糖。

然而,然而,我们没写错误处理。

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

正常情况下 async 函数中return结果会使Promise对象变为 resolved状态,返回值作为then方法回调函数的参数,而出错则会使Promise对象的变为reject状态,错误会被catch捕获。

因为 async函数 相当于对 多个Promise的封装,所以必须等到内部所有的await命令执行完,才会改变自己的状态为resolved,除非 碰到return语句或者抛出了异常。
也就是说,正常情况下 只有async函数内部的异步操作执行完,才会执行then后面的语句。

只要一个await后面的Promise变为rejected,整个async函数就会中断执行,整个async返回的Promise对象就会是rejected状态

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

因为第一个await后面的对象reject了,所以整个async函数就中断执行了

有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。
这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

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

try catch


try catch是JavaScript的异常处理机制,把可能出错的代码放在try语句块中,如果出错了,就会被catch捕获来处理异常。如果不catch 一旦出错就会造成程序崩溃。

如果有多个await命令,可以将其都放在try catch结构中,如果执行出错,catch会去捕获异常

async function f() {
    try {
     await Promise.reject('出错了');
     console.log('上面已经出错了');
     return await Promise.resolve('hello world');
    } catch(e) {
        console.log(e);
    }
  }
  
  f()
  .then(v => console.log(v))

catch会去捕获try代码块中的错误,只要有一个抛出了异常,就不会继续执行,所以上面的代码不会打印上面已经出错了也不会执行return await Promise.resolve('hello world');
因为使用了trycatch 所以 async 是顺利执行完成的,其中的报错 被 try catch处理了,所以异常不会被async返回的Promise的catch捕获,因此async返回的Promise对象状态是resolved。

如果异步函数没有依赖关系,最好并发执行


await 会等待后面的异步操作执行完毕,才会继续执行

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

上面的代码会顺序执行,
如果需要多个await没有相互依赖,最好让他们同时触发,可以使用以下两种方式:

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

推荐阅读更多精彩内容

  • 异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本...
    呼呼哥阅读 7,301评论 5 22
  • Promiese 简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Pr...
    雨飞飞雨阅读 3,352评论 0 19
  • 00、前言Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区...
    夜幕小草阅读 2,129评论 0 12
  • 本文适用的读者 本文写给有一定Promise使用经验的人,如果你还没有使用过Promise,这篇文章可能不适合你,...
    HZ充电大喵阅读 7,299评论 6 19
  • 《奇葩说》有一期“不给别人添麻烦是不是一种美德”,蔡康永有一段话很触动我,大意是:麻不麻烦跟彼此之间的关系有关,关...
    彭焉阅读 758评论 2 9