异步和同步概念
- 同步(Synchronous)意味着事件、操作或进程是有序的,一个操作必须在另一个操作完成后开始执行。
- 异步(Asynchronous)则意味着事件、操作或进程是独立的,可以在不等待其他操作完成的情况下开始执行。
- 异步I/O操作是指程序在发起I/O请求后,无需等待操作完成,可以继续执行其他任务。当异步I/O操作完成时,程序会通过某种方式(如回调函数、事件通知、信号等)得到通知。
我们通过一个setTimeOut的例子理解异步和同步(注意setTimeOut是一个回调函数)
setTimeout(() => {
console.log('3秒计时器');
}, 3000);
console.time();
for(let index = 0; index < 20000000; index++) {
index.toString = '这是' + index;
}//for循环大概需要执行5s
console.timeEnd();
setTimeout(() => {
console.log('2秒计时器');
}, 2000);
//结果:
// default: 5030.498779296875ms
// 3秒计时器(稍慢)
// 2秒计时器(2s后)
原理(同步执行优先,事件队列机制):
setTimeOut是异步函数,传入了一个回调函数,异步执行第一个setTimeout的计时。然后执行for循环,需要循环5s才能执行完毕,在3s时setTimeOut异步执行完成并使用回调函数通知主线程将该回调函数放在事件队列中等待执行,在5s时等主线程的for循环执行完毕,主线程空闲后才会执行setTimeOut传过来的回调函数。最后才会走到第二个setTimeOut。
异步操作四种方法
异步操作完成后需要通知主线程执行回调函数。比如,你发送一个axios 请求,请求成功之后,触发成功的回调函数,请求失败触发失败的回调函数。
1. 回调函数
Node.js最原始的异步操作是使用回调函数(ajax、setTimeout、dom都是)。当一个异步操作完成后,Node.js会调用一个预先设定的函数,这个函数就是回调函数。回调函数的优点是简单直观,缺点是容易造成回调地狱,回调函数不能使用 try catch 捕获错误,不能return 。
这种异步任务的代码,不能保证能按照顺序执行,那如果我们非要代码顺序执行呢?就需要像下面一样多层回调,就是所谓的回调地狱,导致代码难以理解和维护。
setTimeout(function () { //第一层
console.log('武林要以和为贵');
setTimeout(function () { //第二程
console.log('要讲武德');
setTimeout(function () { //第三层
console.log('不要搞窝里斗');
}, 1000)
}, 2000)
}, 3000)
2. Promise
Promise是ES6引入的一种异步处理方式。在回调函数的基础上,它解决了回调地狱的问题,使得异步代码更加易于理解和维护。Promise有三种状态:pending(等待中)、fulfilled(已成功)和rejected(已失败)。Promise对象一旦从pending状态变为fulfilled或rejected状态,就不会再改变。如下例子展示异步操作。
let p = new Promise((resolve,reject) => {
console.log("promise本身是同步");
if("操作为1"){
resolve("then是异步")
}else{
reject("catch是异步");
}
})
p.then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
})
console.log("想不到吧")
//结果:
//promise本身是同步
//想不到吧
//then是异步
如下例子,promise解决回调地狱的问题的方法是链式调用。如下例子,首先testP("0")返回了一个promise,进入pending状态,当resove后进入fulfilled状态,第一个then中输出0并返回一个promise,进入pending状态,当resove后进入fulfilled状态,第二个then中输出1并返回一个promise......以此类推。
function testP(val:string) {
return new Promise((resolve, reject) => {
resolve(val);
});
}
testP("0").then(res1 => {
console.log(res1); //输出0
return testP("1");
}).then(res2 => {
console.log(res2); //输出1
return testP("2");
}).then(res3 => {
console.log(res3); //输出2
return testP("3");
}).catch(err => {
console.log(err);
});
//结果:
//0
//1
//2
一个更复杂一点的例子:
function testP(val,time) {
return new Promise((resolve, reject) => {
setTimeout(()=>{
if(val){
resolve(val);
}else{
reject("err")
}
},time)
});
}
testP(1,1000).then(res1 => {
console.log(res1); //输出1
return testP(2,2000);
}).then(res2 => {
console.log(res2); //输出2
return testP(3,3000);
}).then(res3 => {
console.log(res3); //输出3
});
//结果:
//1
//2
//3
3. async/await
基于Promise的语法糖,本质上就是Promise。它使得异步代码看起来更像同步代码,易于阅读和编写。在async函数中,可以使用await关键字来等待一个Promise对象的结果。看以下代码讲解。
/*===setTimeout是异步函数,由此可见是异步操作,导致console.log先执行===*/
function shunxu() {
try {
setTimeout(()=>{
console.log("计时器完成")
},1000)
console.log("hello");
}
catch (err) {
console.error(err);
}
}
shunxu();
//结果:
//hello
//计时器完成
/*===封装一个返回Promise对象的delay异步方法试试。===*/
function delay(ms) {
return new Promise((resolve,reject) =>{
setTimeout(()=>{
console.log("计时器完成");
resolve("") //返回结果
}, ms)
} );
}
async function shunxu() {
try {
await delay(1000);
console.log("hello");
}
catch (err) {
console.error(err);
}
}
shunxu();
//结果:
//计时器完成
//hello
/*===这个用Promise的then和catch等同于上面那个async/await组合使用===*/
function shunxu() {
delay(1000).then((res)=>{console.log("hello");})
.catch((err)=>{console.error(err);})
}
shunxu();
//结果:
//计时器完成
//hello
//如果不使用async/await或者Promise的then试试。
function delay(ms) {
return new Promise((resolve,reject) =>{
setTimeout(()=>{
console.log("计时器完成");
resolve("") //返回结果
}, ms)
} );
}
function shunxu() {
try {
delay(1000); //这个函数是返回Promise对象的异步函数,不使用await就会异步执行
console.log("hello");
}
catch (err) {
console.error(err);
}
}
shunxu();
//结果:
//hello
//计时器完成
/*===问题来了,以下运行结果,发现shunxun也成为了一个异步函数,===*/
/*===导致console.log()先执行了。有时候特别需要注意这样的使用场景。===*/
function delay(ms) {
return new Promise((resolve,reject) =>{
setTimeout(()=>{
console.log("计时器完成");
resolve("") //返回结果
}, ms)
} );
}
async function shunxu() {
try {
await delay(1000);
console.log("hello");
}
catch (err) {
console.error(err);
}
}
shunxu();
console.log("hhh")
//结果:
//hhh
//计时器完成
//hello
//应当这样使用:
shunxu().then(()=>{console.log("hhh")})
4. Generator函数
关于这个暂时不了解,使用场景少,可以用其他替代。
Promise和async/await的关系
两者本质是一样的,只不过表示不一样而已。如下示例,fun1和fun2是等效的,只是表示方式不一样。
//定义promiseFunction是一个返回Promise对象的函数
function promiseFunction(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("计时器完成");
if(ms > 500){
resolve("结果") //返回结果
}else{
reject("err")
}
}, ms)
});
}
async function func1() {
try {
let res = await promiseFunction(1000);
console.log("执行func1")
return res
}
catch (err) {
console.log(err)
return (err)
}
}
function func2() {
return new Promise((resolve, reject) => {
promiseFunction(1000).then((res) => {
console.log("执行func2")
resolve(res)
}).catch((err) => {
reject(err)
});
})
}
func1().then((res)=>{console.log(res)})
func2().then((res)=>{console.log(res)})
感悟总结
我们常用的就是返回了Promise对象的异步操作函数,比如鸿蒙app获取数据库对象实例的getRdbStore方法,返回Promise对象。
当我们在函数func中调用该方法时,需要这样写(这两种都是一样的):
async createDataBase(){
try{
let res = await store.getRdbStore(context,config)
console("创建数据库操作完成")
}catch(errResult){}
};
createDataBase(){
store.getRdbStore(context,config).then(result => {
let res = result
console("建表操作完成")
}).catch((errResult)={})
}
另外容易出现的问题,和之前讲的一样,createDataBase()成为了一个异步函数。当我需要新建dataBase并创建table时,原本以为这样写:
createDataBase()
createTable()
实际上会先执行createTable()再执行createDataBase(),于是就会报错。可以这么写:
createDataBase().then(()=>{creaeTable()})