由回调函数、Promise到async/await的同步写法执行异步代码

由回调函数、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();
/**
输出:
	1111
	3333
	成功
*/
async function fn1(){  
	return  '成功';
}
async function fn2(){
	console.log(1111);
	// await 将异步代码转为同步结果,等着异步代码执行完才执行后面的代码
	let data = await fn1();  
	console.log(data);
	console.log(3333);
}
fn2();
/**
输出:
	1111
	成功
	3333
*/

同步异步的简单讲解就到这里了,若你有其它看法,欢迎指正,期待您的留言!

求学的三个条件是:多观察、多吃苦、多研究。——加菲劳

原创文章 4 获赞 38 访问量 4014

猜你喜欢

转载自blog.csdn.net/weixin_41967475/article/details/105821978