写在前面
javascript语言的执行环境是"单线程"(single thread),就是指一次只能完成一件任务。如果有多个任务,就必须排队,等前面一个任务完成,再执行后面一个任务,以此类推。
这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。
单线程
function f1() {
console.log('1')
}
function f2() {
console.log('2')
}
f1()
f2()
很容易可以看出,上述代码会依次输出1,2。因为代码是从上到下,依次执行,执行完f1(),才会执行f2()。但是如果f1()中的代码执行的是读取文件或者ajax操作呢,文件的读取都需要一定时间,难道我们需要完全等到文件完全读完再进行写操作么?为了解决这个问题,接下来我们来探究一下js中 同步和异步 的概念。
同步和异步
同步
- 指在 主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
- 也就是调用一旦开始,必须这个调用 返回结果(划重点——)才能继续往后执行。程序的执行顺序和任务排列顺序是一致的。
异步
- 异步任务是指不进入主线程,而进入 任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
- 每一个任务有一个或多个 回调函数。前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行。
- 程序的执行顺序和任务的排列顺序是不一致的,异步的。
- 我们常用的setTimeout和setInterval函数,Ajax都是异步操作。
那么如何实现异步编程呢,笔者介绍几种方法
回调函数(Callback)
回调函数,这是异步编程最基本的方法。
const fs = require('fs')
fs.readFile('./pakage.json',(err,info) => {
fs.writeFile('./p.json',info,(err) => {
if(!err) {
setTimeout(() => {
console.log('ok')
},2000)
}
})
})
上述代码通过回调函数的嵌套,从文件系统中读取一个./pakage.json文件并写入./p.json,读取成功两秒后输出'ok'。用回调来实现异步,没有什么问题。
但是试想,如果再多几个异步函数,代码整体的维护性,可读性都变的极差,如果出了bug,修复过程也变的极为困难,这个便是所谓的 回调函数地狱。
Promise对象
Promise对象用于表示一个异步操作的最终状态(完成或失败),以及其返回的值。
MDN对Promise定义如上,Promise本意为承诺,我们可以理解为程序承诺过一段时间后会给你一个结果。 Promise是一个对象,可以保存三个状态 每一时刻必须有一个状态。
- 成功 Fulfilled
- 失败 Rejected
- 处理中 Pending
- 默认 pending 如果调用 resolve fulfilled
- 默认 pending 如果调用 reject rejeced
const fs = require('fs')
const promise1 = new Promise((resolve,reject) => {
fs.readFile('./package.json',(err,info) => {
resolve(info)
})
})
const promise2 = (info) => {
new Promise((resolve,reject) => {
fs.writeFile('./p.json', info,(err) => {
if(!err) {
resolve();
}else{
reject();
}
})
})
}
const promise3 = (time) => {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve()
},time)
})
}
//then链式调用
//读文件成功 将结果作为参数传入promise2
promise1.then((info) => {
return promise2(info)
})
.then(() => {
// 等着前面的promise
console.log('读写完成')
return promise3(2000)
})
.then( ()=> {
console.log('ok')
})
这么一看,并没有什么区别,还比上面的异步回调复杂,得先新建Promise再定义其回调。但其实,Promise的真正强大之处在于它的多重链式调用,可以避免层层嵌套回调。
我们先使用new
来构建一个promise
。Promise接受一个函数作为参数,该函数的两个参数分别是resolve和reject。resolve
:成功时调用,并将结果,作为参数传递出去;reject
:失败时调用,并将错误,作为参数抛出。
- then方法接收两个函数作为参数,第一个参数是Promise执行成功时的回调,第二个 参数是Promise执行失败时的回调。
- Promise对象的then方法返回一个新的Promise对象,因此所以可以通过链式调用then方法。
我们还可以继续优化一丢丢。
async+await 语法糖
直接上代码
async function run() {
let info = await promise1;
await promise2(info);
await promise3(2000);
console.log('ok');
}
async函数是在ES2017 标准中引入的,使我们异步的代码更加优雅了。这里使用async+await 代替了.then()方法。
- async必须在函数声明前
- await 接一个 promise,那么后面的代码就会等待,等promise resolve了才会执行。