简单介绍下这几个的关系
为方便起见 用以下代码为例简单介绍下这几个东西的关系,
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捕获。
上面的代码就是说
- buildData 这个函数被 async 修饰 说函数中有异步操作
- await 等待异步操作结果
- 如果有错误发生 使用try catch 捕获异常
上面说的太过简单,简要的说明下 各个东西是干啥的。这里的核心是 promise 下面会逐个介绍
Promise
Promise字面上讲,是一个承诺。这个承诺有三个状态
- pending 进行中(悬而未决)
- fulfilled 已成功(以满足)
- rejected 已失败(已拒绝)
从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise对象有以下两个特点:
- 对象的状态不受外界的影响。只有异步操作的结果可以决定当前是哪种状态。
- 状态一旦改变 就不会再变,任何时候都可以得到这个结果。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捕获。
所以:
- 异步操作成功时,把结果告诉resolve回调函数 异步操作失败 把错误告诉reject回调函数
- 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;