啥叫异步?啥叫同步?🙉
同步:
每次只有一个线程其他线程被阻塞
简单来说,就是任务一个接着一个完成。只要一个没完成其他被堵塞
异步:
不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程。
简单来说,就是任务同时进行,中途可能需要等待其他任务完成才能进行当前任务。
js为啥需要异步?🙉
js是单线程的,如果有一个任务我们要花很长时间,那岂不是这之后的代码全被阻塞了。
Js的异步编程发展史👏
其实异步编程的发展的目的是愉快地coding:用同步写法写异步
1. callback回调函数:
回调函数:异步操作执行完后触发执行的函数,类似如下格式:
$.get("http://xxx.xxxx.com/api",callback);
优点:
- 简单
- 符合传统js
缺点:
- 容易形成回调地狱 如果:我们需要请求A接口得到学生id,通过学生id请求B接口得到学生成绩,再通学生成绩请求得到排名(不是很好的例子 但大概这个意思)
fetch({
url: "/student",
data:1,
success: function (id) {
fetch({
url: "/score",
data:id,
success: function (score) {
fetch({
url: "/rank",
data:score,
success: function (rank) {
console.log(rank)
}
})
}
})
}
})
- 回调嵌套会导致代码难以维护
- 并且不方便统一处理错误(不能 try catch)
2. Promise:
Promise 一定程度上解决了回调地狱的问题,Promise 最早由社区提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。Promise对象简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
fetch(1).then(id=> {
return fetch(id);
}).then(score => {
return fetch(score);
}).then(rank=> {
console.log(rank)
}).catch(reason => {
console.log(reason);
});
优点:
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果
- 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
缺点:
- 无法取消 Promise
- 当处于pending状态时,无法得知目前进展到哪一个阶段
- 错误不能被 try catch
3. Generator
Generator 函数是 ES6 提供的一种异步编程解决方案,整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明。 将函数分割出好多个部分,调用一次next就会继续向下执行。返回结果是一个迭代器,迭代器有一个next方法。
function my_co (it) {
return new Promise((resolve, reject) => {
function next(data) {
let {value, done} = it.next(data);
if(!done) {
value.then(val => {
next(val);
}, reject);
}else{
resolve(value);
}
}
next();
});
}
function *getData() {
let res=yield fetch('/student',()=>{});
let res1=yield fetch('/score',res1,()=>{});
let res2=yield fetch('/rank',res2,()=>{});
return res2
}
let it=getData()
let id=it.next(it)
let score=it.next(id)
let rank=it.next(score)
my_co(getData()).then(data => {
console.log(data);
});
特别注意:
看上去似乎我们日常业务中generator的异步写法不常用,但实际上在某些库的开发中它有着重要用途。
对!说的就是那个redux-saga
而且仔细想想还有那个异步编程的方式有这么大的控制性,想停就停!
4. Aync await
async-await其实是一个语法糖,它的实现就是将 Generator函数和自动执行器(co),包装在一个函数中。
async相当于用Promise.resolve()包裹其函数返回值。
await相当于generator和promise的语法糖,相当于yield,但他之外的同步代码可以自动执行
🔔如果没有依赖性的话也可以用promise.all
//如上面那个例子
function fetchData(api) {
let result
let url = 'http://localhost:3000'
fetch(url + api, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then((res) => {
return res.json()
})
.then((json) => {
console.log(json)
result=json
})
return result
}
async function getData(){
let id=await fetchData('/student')
let score=await fetchData('/score')
let rank=await fetchData('/rank')
}
优点:
- 代码清晰,
- 不用像 Promise 写很多 then ,
- 可以处理回调地狱的问题
- 并且错误可以被
try catch。
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
🌰试一试:
git 地址
内含关于四种方式异步编程的例子,欢迎大家试一试👀
附加:
- js是怎么实现异步执行的?
执行栈:
执行栈可以看作是一个存储函数调用的栈结构,遵循先进后出。 执行js时就会往执行栈放入函数。遇到异步则被挂起放入任务队列。
EventLoop:
等执行栈空了,eventloop就会把要执行的代码从队列中放入执行栈。\ - WebWorker是真的多线程吗?详见【WebWorker,这次一定会!】