详解 JavaScript 三种异步方式
前言
JavaScript 是一个单线程语言,但是也不乏一些异步操作,比如定时器,浏览器事件,回调等。还有耳熟能详的 Promise。自从 ES6 出来之后,提供了更丰富的异步方式。比如 async 函数,Generator 函数。今天就给大家分享一下这3种异步的用法以及区别。
在开始讲解这三个异步函数用法之前,我们通过一个案例来体会一下什么叫异步和同步。
1. 异步
setTimeout(function (res) {
console.log(res)
},3000,"我是3秒钟之后打印")
setTimeout(function (res) {
console.log(res)
},2000,"我是2秒钟之后打印")
setTimeout(function (res) {
console.log(res)
},1000,"我是1秒钟之后打印")
打印结果为:
众所周知,setTimeout 是异步函数,异步函数的特点之一就是什么时候到点了,就什么时候执行。这跟 Promise 的命名异曲同工,Promise 是承诺的意思。即承诺什么时候执行,就什么时候执行。所以以上的打印顺序根据计时器的时间判定应该是"我是1秒钟之后打印"、“我是2秒钟之后打印”、“我是3秒钟之后打印”。
2. 同步(使用回调将异步变同步)
那么问题来了。如果想让以上的异步执行顺序变成同步的昵? 即打印顺序为 “我是3秒钟之后打印”、“我是2秒钟之后打印”、“我是1秒钟之后打印”。
在 Promise 、async 和 Generator 函数没有出来之前,我们的做法就是使用回调函数。
function beSync() {
setTimeout(function (res) {
console.log(res);
setTimeout(function (res) {
console.log(res);
setTimeout(function (res) {
console.log(res)
},1000,"我是1秒钟之后打印")
},2000,"我是2秒钟之后打印")
},3000,"我是3秒钟之后打印")
}
beSync();
打印结果为:
3. 分析回调
通过回调实现了将异步变成同步。以上代码有以下几个问题。
- 回调里面层层嵌套,代码冗余;
- 回调太多,不免有些分辨不出,那个参数来源于那个函数的回调;
- 代码之间耦合度太高,一旦有一个回调出现错误,其他回调都会遭殃;
- 不能用 try catch 来捕获错误。
基于以上的问题分析,异步解决方案 Promise 函数、async 函数及Generator 函数应运而生。我们来学习一下这3个函数,并使用这3个函数解决以上问题。
一、Promise 函数
- 用法
var promise = new Promise(function(reslove,reject){
if(){
//异步函数成功
reslove(res)// res是异步函数成功的所获取的参数
}else{
//异步函数失败
reject(err)
}
})
- 示例
var promise = new Promise(function(reslove,reject){
setTimeout(function(res){
reslove(res)
},1000,'成功')
});
promise.then(function(res){
console.log(res);//'成功'
})
- 解决以上案例
var promise = new Promise(function (resolve) {
setTimeout(function (res) {
resolve(res)
},3000,"我是3秒钟之后打印")
})
promise
.then(function (res) {
console.log(res);//"我是3秒钟之后打印"
return new Promise(function (resolve) {
setTimeout(function (res) {
resolve(res)
},2000,"我是2秒钟之后打印")
})
})
.then(function (res) {
console.log(res);//"我是2秒钟之后打印"
return new Promise(function (resolve) {
setTimeout(function (res) {
resolve(res)
},1000,"我是1秒钟之后打印")
})
.then(function (res) {
console.log(res);//"我是1秒钟之后打印"
})
})
打印结果为:
Promise 优点是代码结构看上去很清晰,每一个 then 里面的参数都是上一个异步返回的结果对象。缺点是所有的回调都放在 then 里面,没有语义性。
二、Generator 函数
-
用法
Generator 函数有两个明显特征,一是 function 与函数名之间有*号,二是在函数内部使用 yield 表达式,定义不同的内部状态。yield 表示中止执行,而 next 方法表示开启执行,返回的状态值为 yield 后面的表达式结果。
Generator 函数返回一个遍历器对象。 -
示例
function * hello() {
yield '1';
yield '2';
return 'end'
}
var text = hello();
console.log(text.next());
console.log(text.next());
console.log(text.next());
打印结果为:
- 解决以上案例
function* helloWorld() {
yield new Promise((resolved)=>{
setTimeout(function (res) {
resolved('我是3秒钟之后打印');
}, 3000);
});
yield new Promise((resolved)=>{
setTimeout(function (res) {
resolved('我是2秒钟之后打印');
}, 2000);
});
yield new Promise((resolved)=>{
setTimeout(function (res) {
resolved('我是1秒钟之后打印');
}, 1000);
});
};
var hw = helloWorld();
hw.next().value
.then(function (res) {
console.log(res);
})
.then(function () {
hw.next().value.then(function (res) {
console.log(res);
})
.then(function () {
hw.next().value.then(function (res) {
console.log(res);
});
})
})
三、async 函数
-
用法
async 函数 是 Generator 函数的语法糖,它只是将 Generator 函数中的 * 变成了async ,yield 变成了 await,更具有语义性。
async 函数直接执行,不需要通过 next。
async 函数返回的是一个 promise 对象,可以使用 then 方法添加回调函数。 -
示例
async function func() {
return 'hello';
}
func().then(function (res) {
console.log(res)//'hello'
})
- 解决以上案例
async function helloWorld() {
await new Promise(function (resolve) {
setTimeout(function (res) {
console.log(res);
resolve(res)
}, 3000, '我是3秒钟之后打印');
});
await new Promise(function (resolve) {
setTimeout(function (res) {
console.log(res);
resolve(res)
}, 2000, '我是2秒钟之后打印');
});
await new Promise(function (resolve) {
setTimeout(function (res) {
console.log(res);
resolve(res)
}, 1000, '我是1秒钟之后打印');
});
};
helloWorld()
打印结果为: