这里是修真院前端小课堂,每篇分享文从前端
【背景介绍】【知识剖析】【常见问题】【解决方案】【编码实战】【扩展思考】【更多讨论】【参考文献】程序员
八个方面深度解析前端知识/技能,本篇分享的是:es6
【异步编程有哪几种方法来实现?】web
你们好,我是IT修真院武汉分院web第16期的学员孟晨,一枚正直纯洁善良的web程序员 今天给你们分享一下,修真院官网js(职业)任务五,深度思考中的知识点——异步编程有哪几种方法来实现?编程
1.背景介绍
你可能知道,Javascript语言的执行环境是"单线程"(single thread)。
所谓"单线程",就是指一次只能完成一件任务。若是有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),每每就是由于某一段Javascript代码长时间运行(好比死循环),致使整个页面卡在这个地方,其余任务没法执行。设计模式
为了解决这个问题,Javascript语言将任务的执行模式分红两种:同步(Synchronous)和异步(Asynchronous)。浏览器
"同步模式"就是上一段的模式,后一个任务等待前一个任务结束,而后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则彻底不一样,每个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,因此程序的执行顺序与任务的排列顺序是不一致的、异步的。
"异步模式"很是重要。在浏览器端,耗时很长的操做都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操做。在服务器端,"异步模式"甚至是惟一的模式,由于执行环境是单线程的,若是容许同步执行全部http请求,服务器性能会急剧降低,很快就会失去响应。服务器
2.知识剖析
经常使用的异步编程的几种方法?
首先咱们来看一个基本的例子,在这个例子中输出的顺序是1,3,2,咱们想让他按顺序1,2,3输出就须要用到异步编程的方法异步
function fn1() { async
console.log('Function 1')
}
function fn2() {
setTimeout(() => {
console.log('Function 2')
}, 2000)
}
function fn3() {
setTimeout(() => {
console.log('Function 3')
}, 500)
}
fn1()
fn2()
fn3()
// output =>
// Function 1
// Function 3
// Function 2
壹.回调函数
回调函数是异步编程的方法中最简单也是最经常使用的一个方法
采用这种方式,咱们把同步操做变成了异步操做,f1不会堵塞程序运行,至关于先执行程序的主要逻辑,将耗时的操做推迟执行。
回调函数的优势是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,并且每一个任务只能指定一个回调函数。
若是再嵌套多几层,代码会变得多么难以理解 这个被称之为“回调函数噩梦”(callback hell)!!!
也是能够看看例子,若是按照我们刚刚的写法的话输出顺序会是3,2,1,因此把每一个函数中写成回调函数的形式
就可让执行完了前面的才会执行后面的,而后标红的部分就是被称为回调函数噩梦的缘由,
只是少数嵌套函数的话不明显但多层嵌套代码就会显得很混乱
function fn1(f) {
setTimeout(() => {
console.log('Function 1')
f()
}, 1000)
}
function fn2(f) {
setTimeout(() => {
console.log('Function 2')
f()
}, 2000)
}
function fn3() {
setTimeout(() => {
console.log('Function 3')
}, 500)
}
fn1(function () {
fn2(fn3)
})
// output =>
// Function 1
// Function 2
// Function 3
贰.事件发布/订阅
发布/订阅模式也是诸多设计模式当中的一种,刚好这种方式能够在es5下至关优雅地处理异步操做。什么是发布/订阅呢?以上一节的例子来讲,fn1,fn2,fn3均可以视做一个事件的发布者,只要执行它,就会发布一个事件。这个时候,咱们能够经过一个事件的订阅者去批量订阅并处理这些事件,包括它们的前后顺序。下面咱们基于上一章节的例子,增长一个消息订阅者的方法(为了简单起见,代码使用了es6的写法):
class AsyncFunArr {
constructor(...arr) {
this.funcArr = [...arr]
}
next() {
const fn = this.funcArr.shift()
if (typeof fn === 'function') fn()
}
run() {
this.next()
}
}
const asyncFunArr = new AsyncFunArr(fn1, fn2, fn3)
function fn1() {
console.log('Function 1')
asyncFunArr.next()
}
function fn2() {
setTimeout(() => {
console.log('Function 2')
asyncFunArr.next()
}, 500)
}
function fn3() {
console.log('Function 3')
asyncFunArr.next()
}
// output =>
// Function 1
// Function 2
// Function 3
叁.PROMISE对象
romises对象是CommonJS工做组提出的一种规范,目的是为异步编程提供统一接口。 简单说,它的思想是,每个异步任务返回一个Promise对象,该对象有一个then方法,容许指定回调函数。好比,f1的回调函数f2,能够写成: f1().then(f2); 这样写的优势在于,回调函数变成了链式写法,程序的流程能够看得很清楚,并且有一整套的配套方法,能够实现许多强大的功能。 好比,指定多个回调函数: f1().then(f2).then(f3); 再好比,指定发生错误时的回调函数: f1().then(f2).fail(f3); 并且,它还有一个前面三种方法都没有的好处:若是一个任务已经完成,再添加回调函数,该回调函数会当即执行。因此,你不用担忧是否错过了某个事件或信号。这种方法的缺点就是编写和理解,都相对比较难。
标红处就是所谓的链式写法,这样写带来告终构清晰的好处
function fn1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Function 1')
resolve()
}, 1000)
})
}
function fn2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Function 2')
resolve()
}, 2000)
})
}
function fn3() {
setTimeout(() => {
console.log('Function 3')
}, 500)
}
fn1()
.then(fn2)
.then(fn3)
// output =>
// Function 1
// Function 2
// Function 3
肆.GENERATOR
若是说Promise的使用可以化回调为链式,那么generator的办法则能够消灭那一大堆的Promise特征方法,好比一大堆的then()。
generator函数asyncFunArr()接受一个待执行函数列表fn,异步函数将会经过yield来执行。在异步函数内,经过af.next()激活generator函数的下一步操做。
这么粗略的看起来,其实和发布/订阅模式很是类似,都是经过在异步函数内部主动调用方法,告诉订阅者去执行下一步操做。可是这种方式仍是不够优雅,好比说若是有多个异步函数,那么这个generator函数确定得改写,并且在语义化的程度来讲也有一点不太直观。
function fn1() {
setTimeout(() => {
console.log('Function 1')
}, 1000)
}
function fn2() {
setTimeout(() => {
console.log('Function 2')
}, 2000)
}
function fn3() {
setTimeout(() => {
console.log('Function 3')
}, 500)
}
function* asyncFunArr(...fn) {
fn[0]()
yield fn[1]()
fn[2]()
}
const af = asyncFunArr(fn1, fn2, fn3)
af.next()
// output =>
// Function 1
// Function 2
// Function 3
伍.优雅的ASYNC/AWAIT
使用最新版本的Node已经能够原生支持async/await写法了,经过各类pollyfill也能在旧的浏览器使用。那么为何说async/await方法是最优雅的呢?
有没有发现,在定义异步函数fn2的时候,其内容和前文使用Promise的时候如出一辙?再看执行函数asyncFunArr(),其执行的方式和使用generator的时候也很是相似。
异步的操做都返回Promise,须要顺序执行时只须要await相应的函数便可,这种方式在语义化方面很是友好,对于代码的维护也很简单——只须要返回Promise并await它就好,无需像generator那般须要本身去维护内部yield的执行。
function fn1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Function 1')
resolve()
}, 3000)
})
}
function fn2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Function 2')
resolve()
}, 2000)
})
}
function fn3() {
setTimeout(() => {
console.log('Function 3')
}, 500)
}
async function asyncFunArr() {
await fn1()
await fn2()
await fn3()
}
asyncFunArr()
// output =>
// Function 1
// Function 2
// Function 3
3.常见问题
什么时候使用异步
4.解决方案
在浏览器端,耗时很长的操做都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操做。在服务器端,"异步模式"甚至是惟一的模式,由于执行环境是单线程的,若是容许同步执行全部http请求,服务器性能会急剧降低,很快就会失去响应。
5.代码实战
6.拓展思考
异步的好处: 一、异步流程能够当即给调用方返回初步的结果。
二、异步流程能够延迟给调用方最终的结果数据,在此期间能够作更多额外的工做,例如结果记录等等。
三、异步流程在执行的过程当中,能够释放占用的线程等资源,避免阻塞,等到结果产生再从新获取线程处理。
四、异步流程能够等屡次调用的结果出来后,再统一返回一次结果集合,提升响应效率。
7.参考文献
谈一谈几种处理JavaScript异步操做的办法
Javascript异步编程的4种方法
8.更多讨论
鸣谢
感谢你们观看
BY : 孟晨