js的同步和异步问题通常是指ajax的回调。
如果是同步
调用,程序在发出ajax调用后就会暂停,直到远程服务器产生回应后才会继续运行;而如果是异步调用,程序发出ajax调用后不会暂停,而是立即执行后面的代码,服务器返回信息后会自动触发回调函数进行处理。
相比较而言,异步
调用的性能最佳,程序不会出现卡顿的现象,而同步调用则通常用于需要立即获得结果并实时处理的情况。
promise是ES6,async/await是ES7
异步编程的最高境界就是不关心它是否是异步。async、await很好的解决了这一点,将异步强行转换为同步处理。
async/await与promise不存在谁代替谁的说法,因为async/await是寄生于Promise,Generater的语法糖。
async await 它是基于promise的,为什么es7新增了这个?为了解决大量复杂不易读的Promise异步的问题才出现的。async/await相对于promise来讲,写法更加优雅
async必须声明的是一个function
async test= function () {
return "我是个promise返回值"
};
//语法错误 async必须声明的是一个function
async 是“异步”的意思,所以应该很好理解 async 用于申明一个 异步的function,返回的是一个Promise对象!
async function test() {
return "我是个promise返回值" // return Promise.resolve("我是个promise返回值")
}
test();
// Promise {: "我是个promise返回值"}
由上可以,返回的是一个promise
,不过可以理解为一个状态为fulfilled,并且已经拿到了返回值为"我是个promise返回值"的promise ,或者可以理解为return Promise.resolve(“我是个promise返回值”),同理如果没有返回值也是Promise.resolve(underfind),下面我们来验证下,后面接个回调函数!
async function test() {
return "我是个promise返回值" // return Promise.resolve("我是个promise返回值")
}
test().then(result=>console.log('111',result))
// 111 我是个promise返回值
// Promise {: undefined}
是这样的!那么对待async要像对待Promise一样去对待async的返回值。
await必须是在这个async声明的函数内部使用
,意思就是async和await是配对使用
的,await存在于async的内部
,必须是直系,否则会报错!
async function test() {
function test1() {
await 111;
};
};
// Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
async 函数返回一个 Promise 对象
,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
(为了验证,请看下面代码)
async function test() {
await setTimeout(() => {
console.log("我是不是最开始执行")
}, 1000);
console.log('我什么时候执行');
}
test();
// 我什么时候执行
//(1s后)我是不是最开始执行
我们发现,并不是像我们预期的那样,先打印‘我是不是最开始执行’,而是从下面先开始执行了?。
这里是有条件的
,如果await后面是 promise对象
,会将promise异步操作转为同步
,等待 promise的resolve / reject 返回结果,再接着执行函数体内后面的语句!
( 继续测试下)
async function test() {
await new Promise((resolve, reject) => {
setTimeout(() => {
console.log("我是不是最开始执行")
}, 1000);
});
console.log('我什么时候执行');
}
test();
// (1s后) 我是不是最开始执行
我们发现,这跟预期的还是不一样。最下面的 console.log(‘我什么时候执行’)没执行。
原来是如果await的是一个promise对象,那么要等待这个对象解析完成;
如果没有 resolve 或者 reject ,那么后面的内容就不会执行!
(我们进行修改,再测试一下)
async function test() {
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
console.log("我是不是最开始执行")
}, 1000);
});
console.log('我什么时候执行');
}
test();
// (1s后) 我是不是最开始执行
// (1s后,和上面同时) 我什么时候执行
所以我们可以得出一个结论!
(1)await后面如果不是一个promise对象
,那么它就按正常的js顺序执行
,先执行同步代码,当主线程空闲了,再去执行异步队列的任务!
(2)await后面如果是promise对象
,那么await后面的内容还是相当于在then执行,跟promise的区别在于,如果等待的是一个promise对象,那么要等待这个对象解析完成,如果没有resolve或者reject
,那么后面的内容就不会执行
;如果有resolve或者reject
,那么后面的内容正常执行
。
async function test() {
console.log("async1");
await new Promise((resolve, reject) => {
resolve()
});
console.log('async2');
};
//等价于
async function test() {
console.log("async1");
await new Promise((resolve, reject) => {
resolve()
}).then(() => console.log('async2'));
};
async function fn2() {
await 2
console.log(2)
}
//等价于
async function fn2() {
Promise.resolve(2).then(() => console.log(2))
}
async function test() {
let result = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve('返回的值');
}, 1000);
});
console.log('111',result)
// 上面的等价于Promise.resolve(result).then(() => console.log(‘111’,result));
return result;
};
test().then((data) =>
console.log(data)
)
// (1s后) 111 返回的值
// (1s后) 返回的值
我们可以发现,await 的返回值
就是promise的resolve/reject 返回结果
,然后通过执行async函数 retrun出来,相当于 Promise.resolve("返回结果")!
既然说到了这里,看下面的一道宏任务和微任务的面试题!
async function test() {
console.log("async1");
await new Promise((resolve, reject) => {
resolve(1)
});
console.log('async2');
};
setTimeout(function () {
console.log("setTimeout");
}, 0);
test();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log('end');
相信很多人都会做错,那么我们来分析下!
重点
:发现await后面有个console.log(‘async2’),则根据刚才的知识,我们知道执行完await后面的promise
,那么后面的代码都是在promise.then()里面执行
,所以console.log('async2')是个微任务!
,把它扔进异步队列!所以执行结果是
async1
promise1
end
async2
promise2
setTimeout
async函数相当于对多个Promise的封装
,所以必须等
到内部所有的await命令执行完
,才会改变
自己的状态为resolved
,除非
碰到return语句
或者抛出了异常
。
也就是说,正常情况下 只有async函数内部的异步操作执行完,才会执行then后面的语句。
当async函数抛出异常时
或者await的返回值reject状态
,Promise 的 reject 方法也会传递这个异常值!
async function test() {
throw Error("获取错误")
};
test().catch((error) =>
console.log(error)
);
//Error: 获取错误
只要一个await后面的Promise变为rejected,整个async函数就会中断执行,整个async返回的Promise对象就会是rejected状态。
async function test() {
let result = await new Promise((resolve, reject) => {
setTimeout(() => {
reject("获取返回值失败");
}, 1000);
});
//reject后,后面console.log('111',result)代码不执行了
console.log('111',result)
return result;
};
test().catch((error) =>
console.log('222',error)//获取‘返回值失败’
);
// 222 获取返回值失败
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是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('111',e);
}
}
f()
.then(v => console.log('222',v))
.catch(k => console.log('333' , k))
// 111 出错了
// 222 undefined
catch会去捕获 try 代码块中的错误,只要有一个抛出了异常,就不会继续执行
。
所以上面的代码不会打印“上面已经出错了” ,也不会执行“return await Promise.resolve(‘hello world’)”;
因为使用了try catch 所以 async 是顺利执行完成的,其中的报错 被 try catch处理了,所以异常不会被async返回的Promise的catch捕获,因此async返回的Promise对象状态是resolved。
async await是promise的进化版
,async await 相比原来的Promise的优势在于处理 then链,不必把回调嵌套在then中,只要await 即可
。
我们来模拟下一个场景,有3个请求,但是有顺序的,必须要先执行第一个,再执行第二个,最后执行第三个,并且1秒后执行第一个请求,执行完第一个请求后,3秒后执行第二个请求,执行完第二个请求后,5秒后执行第三个请求!
promise会这样写
let request1 = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('request1返回的值');
}, time * 1000);
});
};
let request2 = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('request2返回的值');
}, time * 1000);
});
};
let request3 = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('request3返回的值');
}, time * 1000);
});
};
request1(1).then((data) => {
console.log(data);//request1返回的值
return request2(3)
}).then((data) => {
console.log(data);//request3返回的值
return request3(5)
}).then((data) => {
console.log(data)//request3返回的值
})
async await 会这样写
async function request() {
let resolve1 = await request1(1);
console.log(resolve1);
resolve2 = await request2(3);
console.log(resolve2);
resolve3 = await request3(5);
console.log(resolve3);
}
request();
对于Promise状态的相关说明,官网上其实已经表达的很清楚了,下面这段话摘自MDN对Promise的描述:
被兑现(fulfilled)
或被拒绝(rejected)
,那么我们也可以说它处于已敲定(settled)状态
。您还会听到一个经常跟 promise 一起使用的术语:已决议(resolved)
,它表示 promise 已经处于已敲定(settled)状态
,或者为了匹配另一个 promise 的状态被"锁定"
了。状态概念解析:resolved状态 = Settled状态 = fulfilled状态 + rejected状态
—————————————————————————————————
(本文部分参考博主「世态炎凉!!」的文章,原文链接:https://blog.csdn.net/ws9029/article/details/111402211)