我们知道JavaScript语言的执行环境是单线程,也就是一次只能完成一个任务。如果有多个任务就必须排队,前面一个任务完成,再执行后面的一个任务
这种模式虽然实现起来简单,执行环境相对单纯,但是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的有的浏览器无响应(假死,往往就是因为某一段JS代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。
为了解决这个问题,JavaScript语言将任务的执行模式分成两种:同步和异步。下面主要介绍异步编程的几种方法:
回调函数是异步操作的最基本的方法
当程序跑起来时,一般情况下,应用程序(application program)会时常通过API调用库里所预先备好的函数。但是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function)。
ajax(url, () => {
// 处理逻辑
})
但是回调函数有一个致命的弱点,就是容易写出回调地狱(Callback hell)。假设有多个请求存在依赖
ajax(url, () => {
// 处理逻辑
ajax(url1, () => {
// 处理逻辑
ajax(url2, () => {
// 处理逻辑
})
})
})
回调地狱的根本问题就是:
嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身
嵌套函数一多,就很难处理错误
当然,回调函数还存在着别的几个缺点,比如不能使用 try catch 捕获错误,不能直接 return。在接下来的几小节中,我们将来学习通过别的技术解决这些问题。
setTimeout比较常用,很多人认为setTimeout是延时多久,那就应该是多久后就执行
其实这个观点是错误的,因为JS是单线程执行的,如果前面的代码影响了性能,就会导致setTimeout不会按时执行
Promist本意是承诺的意思,在程序中的意思就是承诺我过一段时间后会给你一个结果,什么时候会用到异步事件?答案是异步操作,异步操作指可能比较长时间才有结果的才做,比如网络请求,读取本地文件.
Pormise的三种状态
这个承诺一旦从等待状态变成了其他状态就永远不能更改状态,也就是说一旦状态变成了resolved后,就不能再次改变
new Promise((resolve, reject) => {
resolve('success')
// 无效
reject('reject')
})
当我们在构造Promist的时候,构造函数内部的代码是立即执行的
new Promise((resolve, reject) => {
console.log('new Promise')
resolve('success')
})
console.log('finifsh')
// new Promise -> finifsh
Promise.resolve(1)
.then(res => {
console.log(res) // => 1
return 2 // 包装成 Promise.resolve(2)
})
.then(res => {
console.log(res) // => 2
})
Promise也很好的解决了回调地狱的问题
ajax(url)
.then(res => {
console.log(res)
return ajax(url1)
}).then(res => {
console.log(res)
return ajax(url2)
}).then(res => console.log(res))
其实它也是存在一些缺点的,无法取消Promise,错误需要通过回调函数捕获
Generator函数是ES6中提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator最大的特点就是可以控制函数的执行.
我们先看一个列子
function* foo(x) {
let y = 2 * (yield(x + 1))
let z = yield(y / 3)
return (x + y + z)
}
let it = foo(5)
console.log(it.next()) //{ value: 6, done: false }
console.log(it.next(12)) //{ value: 8, done: false }
console.log(it.next(13)) //{ value: 42, done: true }
Generator函数一般见到的不多,其实因为它有点绕,一般会配合co库去使用,当然我们可以通过Generator函数解决回调地狱的问题。
function *fetch() {
yield ajax(url, () => {})
yield ajax(url1, () => {})
yield ajax(url2, () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
特点:
如果一个函数加上async,那么这个函数就会返回一个Promise
async function test() {
return "1"
}
console.log(test()) // -> Promise {: "1"}
async就是将函数返回值使用Promise.resolve()包裹了下,和then中处理返回值一样,并且await只能配合async使用.
async function test() {
let value = await sleep()
}
async/await可以说是异步终极解决方案,相比直接使用Promise来说,优势在于处理then的调用链,并且也能够优雅地解决回调地狱问题。
当然也有一些缺点,因为await将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了await会导致性能上的降低
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch(url)
await fetch(url1)
await fetch(url2)
}
async/await函数对Generator函数的改进体现: