前言
众所周知 Javascript
是单线程模型语言,同时只能执行一个任务,其他任务都必须在后面排队等待。
因此,异步处理就成为 Javascript
处理多任务时提升效率最重要的方式之一,这也是 Javascript
区别于其他语言的重要特征。
本文将通过对比三种目前最流行的异步处理方式,让读者深刻体会不同的异步处理方式的优缺点,进一步感受 Javascript
异步处理的独特魅力。
三强选手
1. callback
首先登场的是 callback
(回调函数),它是异步操作最基本的方法。
callback
的具体介绍如下。
下面是两个函数f1
和f2
,编程的意图是f2
必须等到f1
执行完成,才能执行。
function f1() {
// ...
}
function f2() {
// ...
}
f1();
f2();
上面代码的问题在于,如果f1
是异步操作,f2
会立即执行,不会等到f1
结束再执行。
这时,可以考虑改写f1
,把f2
写成f1
的回调函数。
function f1(callback) {
// ...
callback();
}
function f2() {
// ...
}
f1(f2);
回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。
2. Promise
二号选手是 Promise
,它是异步编程的一种新的解决方案。
所谓
Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise
是一个对象,从它可以获取异步操作的消息。Promise
提供统一的API
,各种异步操作都可以用同样的方法进行处理。
下面是一个Promise
对象的简单例子。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
Promise
解决了callbak
回调地狱的问题,异步处理的表达流程也更加清晰,但是多层嵌套带来的繁琐写法,也是困扰Promise
的难点。
3. async
最后一位选手是async
,async
函数是什么?一句话,它就是 Generator
函数的语法糖。
具体代码展示如下。
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
async
函数的最大特点,就是可以将异步处理编写成同步处理,可读性是最强的。相应的,它对开发者的理解难度和技能要求也是最高的。
比赛规则
1. 比赛内容
通过js
代码依次读取files
文件夹中的三个文件——a.json
、b.json
、c.json
的内容。
a.json
的内容如下:
{
"next":"b.json",
"msg":"this is a"
}
b.json
的内容如下:
{
"next":"c.json",
"msg":"this is b"
}
c.json
的内容如下:
{
"next": null,
"msg":"this is c"
}
首先读取a.json
的内容,然后获取next
再读取b.json
,最后读取b.json
的next
获取c.json
,这是最典型的js
异步处理的问题之一。
2. 比赛环境
Node.js
是服务器端运行Javascript
的重要环境,可以为js
提供读取文件的接口,因此,本次比赛也将在Node.js
(8.0以上)的环境下进行。
Node.js
提供的比赛工具如下:
const fs = require('fs') // 文件读写接口,获取json文件的内容
const path = require('path') // 文件路径接口,获取files文件夹里面三个文件的文件路径
比赛结果
1. callback
首先是callback
登场,它给出的解决方式如下:
// callback 方式获取一个文件的内容
function getFileContentByCallback(fileName, callback) {
// path 获取文件路径
const fullFileName = path.resolve(__dirname, 'files', fileName)
// fs 读取文件
fs.readFile(fullFileName, (err, data) => {
if (err) {
console.error(err)
return
}
callback(
JSON.parse(data.toString())
)
})
}
// 读取文件
getFileContentByCallback('a.json', aData => {
console.log('a data callback: ', aData)
getFileContentByCallback(aData.next, bData => {
console.log('b data callback: ', bData)
getFileContentByCallback(bData.next, cData => {
console.log('c data callback: ', cData)
})
})
})
// 输出结果
a data callback: { next: 'b.json', msg: 'this is a' }
b data callback: { next: 'c.json', msg: 'this is b' }
c data callback: { next: null, msg: 'this is c' }
2. promise
接下来是promise
登场,它的解决方式如下:
// promise 方式获取一个文件的内容
function getFileContentByPromise(fileName) {
const promise = new Promise((resolve, reject) => {
// path 获取文件路径
const fullFileName = path.resolve(__dirname, 'files', fileName)
// fs 读取文件
fs.readFile(fullFileName, (err, data) => {
if (err) {
reject(err)
return
}
resolve(
JSON.parse(data.toString())
)
})
})
return promise
}
// 读取文件
getFileContentByPromise('a.json').then(aData => {
console.log('a data promise: ', aData)
return getFileContentByPromise(aData.next)
}).then(bData => {
console.log('b data promise: ', bData)
return getFileContentByPromise(bData.next)
}).then(cData => {
console.log('c data promise: ', cData)
})
// 输出结果
a data promise: { next: 'b.json', msg: 'this is a' }
b data promise: { next: 'c.json', msg: 'this is b' }
c data promise: { next: null, msg: 'this is c' }
3. async
最后登场的是async
,它的解决方式如下:
// async 方式获取一个文件的内容
async function getFileContentByAsync(fileName) {
try {
// getFileContentByPromise 是上面promise获取文件内容的处理函数,aysnc直接调用
const aData = await getFileContentByPromise(fileName)
const bData = await getFileContentByPromise(aData.next)
const cData = await getFileContentByPromise(bData.next)
console.log('a data async: ', aData)
console.log('b data async: ', bData)
console.log('c data async: ', cData)
} catch (error) {
console.error(error)
}
}
// 读取文件
getFileContentByAsync('a.json')
// 输出结果
a data async: { next: 'b.json', msg: 'this is a' }
b data async: { next: 'c.json', msg: 'this is b' }
c data async: { next: null, msg: 'this is c' }
比赛总结
从比赛结果来看,它们都可以顺利完成任务,从实现方式来看,却各不相同。
本次比赛并非比较三者到底谁更强,而是通过最直观的对比方式,让大家感受js
异步处理的多样性,以及每种异步处理的特点和差异性。
callback
利于理解、学习成本低,Promise
承上启下、中流砥柱,async
集大成者、代表未来。
最后附上比赛的GitHub地址:github.com/jiangjiahen…
祝工作顺利,生活幸福。