由回调函数、Promise到async/await的同步写法执行异步代码
同步异步是前端面试中经常遇到的问题,虽然不难,但是搞清楚两者之间的关系和转换还是很重要
同步
同步是一种线性执行的方式,执行的流程不能跨越,其后的线程要阻塞等待前面线程的运行;同步可以保证顺序一致,但是容易导致阻塞,即同步是阻塞模式。
同步一般用于流程性比较强的程序,比如用户登录功能就是同步处理的,需要用户通过用户名和密码验证后才能进入系统。
最基础的JavaScript就是同步的,单线程,自上而下运行。
通俗的话说:同步就相当于是 当客户端发送请求给服务端,在等待服务端响应的请求时,客户端不做其他的事情。当服务端做完了才返回到客户端。这样的话客户端需要一直等待,导致用户体验比较差。
异步
异步是一种并行处理的方式,不必等待一个程序执行完,就可以执行其它的任务,即异步是非阻塞模式。在程序中异步处理的结果通常使用回调函数来处理结果。
异步可以解决阻塞问题,但是会改变顺序性。
通俗的话说:异步就是,当客户端发送给服务端请求时,在等待服务端响应的时候,客户端可以做其他的事情,这样节约了时间,提高了效率。
为什么会有异步
简单来说,没有异步只有同步的话,代码只能自上而下执行,若前面的代码解析时间很长,那么下面的代码就会被阻塞;而对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验,这时我们就需要异步来优化代码。
ES6新特性:Promise
promise 用同步的写法执行异步代码
Promise 优化了回调函数的用法,让原本需要纵向一层一层嵌套的回调函数实现了横向的调用,也就是链式调用
首先我们模拟一个异步时间,要求1s后输出hello world
function fn(){
setTimeout(()=>{
var call = "hello world";
}, 1000);
}
问题来了,我们该如何输出hello world呢?
是这样吗?
function fn(){
setTimeout(()=>{
var call = "hello world";
}, 1000);
return call;
}
console.log(fn()); //Uncaught ReferenceError: call is not defined
不不不,这样会报错。这是因为setTimeout是异步执行,1s后才会执行 var call = “hello world”;这时会先执行return call;所以call is not defined
还是这样?
function fn(){
setTimeout(()=>{
var call = "hello world";
return call;
}, 1000);
}
console.log(fn()); //undefined
不不不,这样也达不到预期效果!这是因为执行 console.log(fn()) 时,fn()没有返回值,所以undefined;而1s后再执行setTimeout
ES5:通过回调函数来处理异步执行的结果
function fn(callback){
setTimeout(()=>{
var call = "hello world";
callback(call);
}, 1000);
}
fn(function(call){
console.log(call); //hello world
});
通过回调函数,我们得到了想要的结果;但是在整体结构上,我们多了一个回调函数,这样看起来不太友好。于是,ES6的Promise诞生了!
ES6:通过Promise来处理异步执行的结果
function fn(resolve,reject){
setTimeout(()=>{
var call = "hello world";
resolve(call);
}, 1000);
}
let p = new Promise(fn);
p.then(function(res){
console.log(res); //hello world
})
Promise是同步的,它里面执行到异步任务以前都是同步执行的。当执行的异步任务的时候,就被挂起了,然后继续执行主线程Promise后面的代码。当异步任务有结果返回的时候,Promise的状态就改变啦!
Promise 构造函数接受一个函数作为参数,函数里面有两个参数 resolve 和 reject ,其中 resolve 作为执行成功的函数, reject 作为执行失败的函数
下面我们以一个小例子来讲解一下resolve和reject
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<button type="button">按钮</button>
<script type="text/javascript">
let oBtn = document.querySelector('button');
//定义一个开关,通过按钮来控制开关的值是true还是false
let Off = true;
oBtn.onclick = function(){
function fn(resolve,reject){
setTimeout(()=>{
var call = "hello world";
if(Off){
Off = !(Off);
resolve(call);
}else{
Off = !(Off);
reject('获取失败!');
}
}, 1000);
}
let p = new Promise(fn);
p.then((res) => {
console.log(res);//hello world
},(res) => {
console.log(res);//获取失败!
})
}
</script>
</body>
</html>
多次点击按钮,上述代码会依次输出如图所示结果:
ES7新特性:async…await
1. async 将普通方法转为 异步并且返回 promise对象
2. await 将异步代码转为同步结果,等着异步代码执行完才执行后面的代码
怎么拿到异步数据?
方法一:
async 将普通方法转为 异步并且返回 promise对象
//async 将普通方法转为 异步并且返回 promise对象
async function test(){
return 'goods';
}
console.log(test());//Promise {<resolved>: "goods"}
let p = test();
p.then(function(res){
console.log(res); //goods
})
方法二:
await 必须在异步函数中使用
async function test(){
return 'goods';
}
//await 必须在异步函数中使用
async function fn(){
let data = await test();
console.log(data); //goods
}
fn();
await 将异步代码转为同步结果,等着异步代码执行完才执行后面的代码
请仔细对比下面两个例子的输出!
async function fn1(){
return '成功';
}
async function fn2(){
console.log(1111);
let p = fn1();
p.then(function(res){
console.log(res);
});
console.log(3333);
}
fn2();
async function fn1(){
return '成功';
}
async function fn2(){
console.log(1111);
// await 将异步代码转为同步结果,等着异步代码执行完才执行后面的代码
let data = await fn1();
console.log(data);
console.log(3333);
}
fn2();
同步异步的简单讲解就到这里了,若你有其它看法,欢迎指正,期待您的留言!
求学的三个条件是:多观察、多吃苦、多研究。——加菲劳