一次性让你懂async/await,解决回调地狱
什么是async?
欢迎留言讨论
async 函数是 Generator 函数的语法糖。使用 关键字 async 来表示,在函数内部使用 await 来表示异步。相较于 Generator ,async 函数的改进在于下面四点:
- 内置执行器。
Generator 函数的执行必须依靠执行器,而 async 函数自带执行器,调用方式跟普通函数的调用一样 - 更好的语义。
async 和 await 相较于 * 和 yield 更加语义化 - 更广的适用性。
co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise对象。而 async 函数的 await 命令后面则可以是 Promise 或者 原始类型的值(Number,string,boolean,但这时等同于同步操作) - 返回值是 Promise。
async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用
此处总结参考自:理解async/await
async 是ES7新出的特性,表明当前函数是异步函数,不会阻塞线程导致后续代码停止运行。
怎么用
申明之后就可以进行调用了
async function asyncFn() {
return 'hello world';
}
asyncFn();
这样就表示这是异步函数,返回的结果
async 表示函数里有异步操作
await 表示紧跟在后面的表达式需要等待结果。
返回的是一个promise 对象,状态为resolved ,参数是return 的值。那再看下面这个函数
async function asyncFn() {
return '我后执行'
}
asyncFn().then(result => {
console.log(result);
})
console.log('我先执行');
上面的执行结果是先打印出'我先执行' ,虽然是上面asyncFn() 先执行,但是已经被定义异步函数了,不会影响后续函数的执行。
现在理解了async 基本的使用,那还有什么特性呢?
async 定义的函数内部会默认返回一个promise 对象,如果函数内部抛出异常或者是返回reject ,都会使函数的promise 状态为失败reject 。
async function e() {
throw new Error('has Error');
}
e().then(success => console.log('成功', success))
.catch(error => console.log('失败', error));
我们看到函数内部抛出了一个异常 ,返回reject ,async 函数接收到之后,判定执行失败进入catch ,该返回的错误打印了出来。
async function throwStatus() {
return '可以返回所有类型的值'
}
throwStatus().then(success => console.log('成功', success))
.catch(error => console.log('失败', error));
成功 可以返回所有类型的值
复制代码
async`函数接收到返回的值,发现不是`异常`或者`reject`,则判定成功,这里可以`return`各种数据类型的值,`false`,`NaN`,`undefined`...总之,都是`resolve
但是返回如下结果会使async 函数判定失败reject
- 内部含有直接使用并且未声明的变量或者函数。
- 内部抛出一个错误
throw new Error 或者返回reject 状态return Promise.reject('执行失败') - 函数方法执行出错(🌰:Object使用push())等等…
还有一点,在async 里,必须要将结果return 回来,不然的话不管是执行reject 还是resolved 的值都为undefine ,建议使用箭头函数。
其余返回结果都是判定resolved 成功执行。
async function PromiseError() {
return Promise.reject('has Promise Error');
}
async function PromiseError() {
Promise.reject('这是错误的做法');
}
PromiseError().then(success => console.log('成功', success))
.catch(error => console.log('失败', error));
我们看到第二行多了个Promise 对象打印,不用在意,这个是在Chrome 控制台的默认行为,我们平常在控制台进行赋值也是同样的效果。如果最后执行语句 或者表达式 没有return 返回值,默认undefined ,做个小实验。
var a = 1;
------------------------------------------------------------
console.log(a);
------------------------------------------------------------
function a(){ console.log(1) }
a();
------------------------------------------------------------
function b(){ return console.log(1) }
b();
------------------------------------------------------------
function c(){ return 1}
c();
------------------------------------------------------------
async function d(){
'这个值接收不到'
}
d().then(success => console.log('成功',success));
-----------------------------------------------------------
async function e(){
return '接收到了'
}
e().then(success => console.log('成功',success));
最后一行Promise { <resolved> : undefined } 是因为返回的是console.log 执行语句,没有返回值。
d().then(success => console.log('成功',success)}
等同于
d().then(function(success){
return console.log('成功',success);
});
js本身是单线程的,通过v8我们可以拥有"异步"的能力
认识完了async,来讲讲await。
await是什么?
await 意思是async wait(异步等待)。这个关键字只能在使用async 定义的函数里面使用。任何async 函数都会默认返回promise ,并且这个promise 解析的值都将会是这个函数的返回值,而async 函数必须等到内部所有的 await 命令的 Promise 对象执行完,才会发生状态改变。
打个比方,await是学生,async是校车,必须等人齐了再开车。
就是说,必须等所有await 函数执行完毕后,才会告诉promise 我成功了还是失败了,执行then 或者catch
async function awaitReturn() {
return await 1
};
awaitReturn().then(success => console.log('成功', success))
.catch(error => console.log('失败',error))
在这个函数里,有一个await 函数,async会等到await 1 这一步执行完了才会返回promise 状态,毫无疑问,判定resolved 。
*很多人以为await 会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上await 是一个让出线程的标志 。await 后面的函数会先执行一遍(比如await Fn()的Fn ,并非是下一行代码),然后就会跳出整个async 函数来执行后面js栈的代码。等本轮事件循环执行完了之后又会跳回到async 函数中等待await***后面表达式的返回值,如果返回值为非promise 则继续执行async 函数后面的代码,否则将返回的promise 放入Promise 队列(Promise的Job Queue)
来看个简单点的例子
const timeoutFn = function(timeout){
return new Promise(function(resolve){
return setTimeout(resolve, timeout);
});
}
async function fn(){
await timeoutFn(1000);
await timeoutFn(2000);
return '完成';
}
fn().then(success => console.log(success));
这里本可以用箭头函数写方便点,但是为了便于阅读本质,还是换成了ES5写法,上面执行函数内所有的await函数才会返回状态,结果是执行完毕3秒后才会弹出’完成 ’。
正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolve 的 Promise。
也可以这么写
function timeout(time){
return new Promise(function(resolve){
return setTimeout(function(){
return resolve(time + 200)
},time);
})
}
function first(time){
console.log('第一次延迟了' + time );
return timeout(time);
}
function second(time){
console.log('第二次延迟了' + time );
return timeout(time);
}
function third(time){
console.log('第三次延迟了' + time );
return timeout(time);
}
function start(){
console.log('START');
const time1 = 500;
first(time1).then(time2 => second(time2) )
.then(time3 => third(time3) )
.then(res => {
console.log('最后一次延迟' + res );
console.timeEnd('END');
})
};
start();
这样用then链式回调的方式执行resolve
START
第一次延迟了500
第二次延迟了700
第三次延迟了900
最后一次延迟1100
END
用async/await呢?
async function start() {
console.log('START');
const time1 = 500;
const time2 = await first(time1);
const time3 = await second(time2);
const res = await third(time3);
console.log(`最后一次延迟${res}`);
console.log('END');
}
start();
达到了相同的效果。但是这样遇到一个问题,如果await 执行遇到报错呢
async function start() {
console.log('START');
const time1 = 500;
const time2 = await first(time1);
const time3 = await Promise.reject(time2);
const res = await third(time3);
console.log(`最后一次延迟${res}`);
console.log('END');
}
start();
返回reject后,后面的代码都没有执行了,以此迁出一个例子:
let last;
async function throwError() {
await Promise.reject('error');
last = await '没有执行';
}
throwError().then(success => console.log('成功', last))
.catch(error => console.log('失败',last))
其实async 函数不难,难在错处理上。
上面函数,执行的到await 排除一个错误后,就停止往下执行,导致last 没有赋值报错。
async 里如果有多个await函数的时候,如果其中任一一个抛出异常或者报错了,都会导致函数停止执行,直接reject ;
怎么处理呢,可以用try/catch ,遇到函数的时候,可以将错误抛出,并且继续往下执行。
let last;
async function throwError() {
try{
await Promise.reject('error');
last = await '没有执行';
}catch(error){
console.log('has Error stop');
}
}
throwError().then(success => console.log('成功', last))
.catch(error => console.log('失败',last))
这样的话,就可以继续往下执行了。
来个🌰练习下
function testSometing() {
console.log("testSomething");
return "return testSomething";
}
async function testAsync() {
console.log("testAsync");
return Promise.resolve("hello async");
}
async function test() {
console.log("test start...");
const testFn1 = await testSometing();
console.log(testFn1);
const testFn2 = await testAsync();
console.log(testFn2);
console.log('test end...');
}
test();
var promiseFn = new Promise((resolve)=> {
console.log("promise START...");
resolve("promise RESOLVE");
});
promiseFn.then((val)=> console.log(val));
console.log("===END===")
执行结果
我们一步步来解析
首先test() 打印出test start...
然后 testFn1 = await testSomething(); 的时候,会先执行testSometing() 这个函数打印出“testSometing ”的字符串。
testAsync() 执行完毕返回resolve ,之后await 会让出线程就会去执行后面的,触发promiseFn 打印出“promise START... ”。
接下来会把返回的Promiseresolve("promise RESOLVE") 放入Promise队列(Promise的Job Queue),继续执行打印“===END=== ”。
等本轮事件循环执行结束后,又会跳回到async 函数中(test() 函数),等待之前await 后面表达式的返回值,因为testSometing() 不是async 函数,所以返回的是一个字符串“return``testSometing ”。
test() 函数继续执行,执行到testFn2() ,再次跳出test() 函数,打印出“testAsync ”,此时事件循环就到了Promise的队列,执行promiseFn.then((val)=> console.log(val)); 打印出“promise RESOLVE ”。
之后和前面一样 又跳回到test函数继续执行console.log(testFn2) 的返回值,打印出“hello async ”。
最后打印“test end... ”。
加点料,让testSomething() 变成async
async function testSometing() {
console.log("testSomething");
return "return testSomething";
}
async function testAsync() {
console.log("testAsync");
return Promise.resolve("hello async");
}
async function test() {
console.log("test start...");
const testFn1 = await testSometing();
console.log(testFn1);
const testFn2 = await testAsync();
console.log(testFn2);
console.log('test end...');
}
test();
var promiseFn = new Promise((resolve)=> {
console.log("promise START...");
resolve("promise RESOLVE");
});
promiseFn.then((val)=> console.log(val));
console.log("===END===")
执行结果
和上一个例子比较发现promiseFn.then((val)=> console.log(val)); 先于console.log(testFn1) 执行。
原因是因为现在的版本async函数会被await resolve,
function testSometing() {
console.log("testSomething");
return "return testSomething";
}
console.log(Object.prototype.toString.call(testSometing))
console.log(Object.prototype.toString.call(testSometing()))
async function testSometing() {
console.log("testSomething");
return "return testSomething";
}
console.log(Object.prototype.toString.call(testSometing))
console.log(Object.prototype.toString.call(testSometing()))
testSomething() 已经是async函数,返回的是一个Promise对象需要等它 resolve 后将当前Promise 推入队列,随后先清空调用栈,所以会"跳出" test() 函数执行后续代码,随后才开始执行该 Promise。
更新
今天是2019-11-06 12:03:46,收到几位掘友的反馈,最后一个例子的执行时机不符合现有版本的结果,原因可能是在文章完结后至今的一年半内 Chrome V8 更新导致,如果有 Opera 、其他使用了 Chrome 老版本内核的浏览器,或者到 这个地址 下载旧版本 Chrome,笔者测试使用的是 Opera,在 Chrome V8 release 文档或里并未找到相关说明。
之前的版本中,await 的函数需要3次tick才会被执行,在当前的版本,v8团队为了提升性能,违反了规范,没有严格按照 promise-resolve-functions 的第13步执行,导致3次tick减少2次。原文
在Node 11,机制已经和浏览器统一,从表面上,你大可以理解 await AsyncFunction 返回的 Promise 为同步任务。
后话
越来越多的人正在研究据说是异步终极编程解决方案的async/await,但是大部分人对这个方法内部怎么执行的还不是很了解,整理了await之后js的执行顺序,希望对你们有所帮助
- 是一种编写异步代码的新方法。之前异步代码的方案是callback和promise。
- 建立在 promise 的基础上,与promise一样也是非阻塞的。
- async/await 让异步代码看起来、表现起来更像同步代码。这正是其威力所在。
参考文献:理解 JavaScript 的 async/await
转自掘金大佬
|