2019-08-28 async await 技巧和误区

async函数的返回值是 Promise 对象

怎么理解上面这句话呢?我们知道,普通的函数的执行结果要有返回值的话,需要用return关键字,没有return关键字则返回值是 undefined

  const f = () => {
    doSomething()...
  }

  const res1 = f() // undefined

  const g = () => {
    doSomething()...
    return 'hello world'
  }

const res2 = g() // 'hello world'

但是async 函数不一样,async函数一定有返回值,返回值是一个promise 对象;这是怎么做到的呢?async函数中的 return 关键字有什么用呢?

  • 如果async函数中没有 return 语句,async函数会自动返回Promise.resolve(undefined),于是就返回了一个Promise对象

  • 如果async函数return一个直接量xxx,async函数会自动返回Promise.resolve(xxx),于是返回的Promise对象的状态是resolved。在调用async函数后,接收 return 返回的值只能用then方法。

async function testAsync() {
    const res = await fetch('xxx') //返回`hello async`
    doSomething()...
    return res;
}

testAsync().then(v => {
    console.log(v);    // 输出 hello async
});
console.log('123')

async函数内部,await后面如果是一个 Promise 对象,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果,然后继续向下执行。
async函数外部,调用async函数后,无论里面是否有耗时操作,都会立即返回,继续向下执行。所以,上面代码,会先输出123,再输出hello async

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误
async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被外面添加的catch方法回调函数接收到。
任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

如果我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。(ps:因为async函数里面有try catch捕获了异常,没有抛到外面来,所以外面async函数返回的promise不会被reject)

如果await后面的异步操作出错,又没有在async函数内被我们主动catch,那么async函数返回的 Promise 对象就会被reject。

最佳实践

async await 本来就是来替代 promise ...then...的,写成同步的语法,比一连串的then简洁很多,但是,async 返回的还是一个promise对象,怎么处理这里的矛盾呢?
我的建议是,在async函数中,加入try catch,所有的逻辑写在try catch中,并且一般情况下不需要return xxx,直接把后续逻辑在async函数中写完,而不是return xxx后,在 then中写后续逻辑;不过这样一来,发生错误后,被里面的catch捕获了,返回的promise仍然是resolved,如果要在外层catch中处理错误,而不是在async中处理错误,可以在里面catch到错误后,调用Promise.reject()来手动让外层catch到错误。

show me code :

const f1 = async () => {
    console.log('第一个function start');
    const res = await new Promise((resolve) => {
        setTimeout(() => {
            console.log('第一个async');
            resolve('f1 resolved');
        }, 3000);
    });
    console.log('第一个function end');
    return res;
};
const f2 = async () => {
    console.log('第二个function start');
    const res = await new Promise((resolve) => {
        setTimeout(() => {
            console.log('第二个async');
            resolve('f2 resolved');
        }, 3000);
    });
    console.log('第二个function end');
    return res;
};
const test1 = () => {
    const res1 = f1();
    const res2 = f2();
    console.log(123456789, res1, res2);
};
test1();
// output:
// 同时:
// 第一个function start
// 第二个function start
// 123456789,  两个 promise 对象
// 3s后 同时:
// 第一个async
// 第二个async
// 第一个function end
// 第二个function end
// 这里 `f1` 和 `f2` 是同步执行的

const test2 =  () => {
    f1().then(res => {console.log(res);});
    f2().then(res => {console.log(res);});
    console.log(123456789);
};
test2();
// output:
// 同时:
// 第一个function start
// 第二个function start
// 123456789,  两个 promise 对象
// 3s后 同时:
// 第一个async
// 第二个async
// 第一个function end
// 第二个function end
// f1 resolved
// f2 resolved
// 和上面一样 这里 `f1` 和 `f2` 是同步执行的

const test3 = async () => {
    const res1 = await f1();
    const res2 = await f2();
    console.log(123456789, res1, res2);
};
test3();
// output: 
// 立即
// 第一个function start
// 3s 后 同时打印:
// 第一个async
// 第一个function end
// 第二个function start
// 再3s后
// 第二个async
// 第二个function end
// 123456789 "f1 resolved" "f2 resolved"

总结: async 函数抽出后,要想执行 async 函数时是等待阻塞的,还得在外面嵌套async函数, 因为 async 函数返回的还是 Promise 对象


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

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

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

推荐阅读更多精彩内容