什么是异步?
所谓异步,简单来说就是异步任务(不会马上就完成的任务);但是js不会等待你这个任务完成,而是直接执行下边的任务;等到你上边的任务完成之后才会去执行相应的逻辑。比如js读取文件就是异步的过程。
异步编程的语法目标,就是怎样让它更像同步编程
1、回调函数
场景: 读取一个文件
let fs = require('fs') fs.readFile('./1.txt', 'utf8', function(err, data){ // 回调的特点是第一个参数一般为错误对象 if (err) { // 如果err有值说明程序出错了 console.log(err) } else { // 否则表示成功获取到数据data console.log(data) } })
当然回调函数也有它的缺点:
- 无法捕获错误(使用try catch)
funnction readFile (fileName) { fs.readFile(fileName, 'utf8', function (data) { if (err) { console.log(err) } else { console.log(data) } }) } try { readFile('./1.txt') } catch (e) { // 如果上边读取文件出错,获取不到错误信息 console.log('err', e) }
- 不能return
// readFile 方法中无法返回读取到文件的内容(data)
- 回调地狱;比如请求一个页面列表,一般服务器最少要读取两个文件,模板文件和数据;这样就会出现嵌套的问题
fs.readFile('./template.txt', 'utf8', function (err, template) { fs.readFile('./data.txt', 'utf8', function (err, data) { console.log(template, data) }) }) // 这样的代码称为恶魔金字塔;且有以下问题 // 1、代码非常难看 // 2、难以维护 // 3、效率比较低,因为它们是串行的;一次只能请求一个文件
2、事件发布订阅(为了解决回调嵌套的问题)
let EventEmitter = require('events') // nodejs核心模块之一;包含两个核心方法 on >> 表示注册监听 emit >> 表示发射事件 let eve = new EventEmitter() let html = {} // 存放页面模板和数据 eve.on('onloading', function (key, value) { html[key] = value if (Object.keys(html).length == 2) { console.log(html) } }) fs.readFile('./template.txt', 'utf8', function (err, template) { eve.emit('onloading', template) // 触发onloading事件,执行事件的回调函数向html中填入模板 }) fs.readFile('./data.txt', 'utf8', function (err, template) { eve.emit('onloading', template) // 触发onloading事件,执行事件的回调函数向html中填入数据 })
3、哨兵变量 (同样处理回调嵌套的问题) 事件发布订阅已经可以解决回调嵌套的问题,但是还需要引入events模块; 利用哨兵变量一样可以解决回调嵌套的问题,且不需要引入其他模块
// 定义一个哨兵函数来处理 function done (key, value) { html[key] = value if (Object.keys(html).length == 2) { console.log(html) } } fs.readFile('./template.txt', 'utf8', function (err, template) { done('template', template) }) fs.readFile('./data.txt', 'utf8', function (err, template) { done('data', data) }) // 可以封装一个高阶函数去生成哨兵函数 function render (length, cb) { let htm = {} return function (key, value) { html[key] = value if (Object.keys(html).length == length) { cb(html) } } } let done = render(2, function (html) { console.log(html) })
4、Promise 上述方法都是用回调函数来处理异步;我们的目标是把异步往同步的方向靠拢
let promise1 = new Promise(function (resolve, reject) { fs.readFile('./1.txt', 'utf8', function (err, data) { resolve(data) }) }) promise1 .then(function (data) { console.log(data) })
5、生成器Generator 当我们在调用一个函数的时候,它并不会马上执行,而是需要我们手动的去执行迭代操作(next方法);简单来说,调用生成器函数会返回一个迭代器,可以用迭代器来执行遍历每个中断点(yield) 调用next方法会有返回值value,是生成器函数对外输出的数据;next方法还可以接受参数,是向生成器函数内部输入的数据
- 生成器简单使用
// 方法名前边加*就是生成器函数 function *foo () { var index = 0; while (index < 2) { yield index++; //暂停函数执行,并执行yield后的操作 } } var bar = foo(); // 返回的其实是一个迭代器 console.log(bar.next()); // { value: 0, done: false } console.log(bar.next()); // { value: 1, done: false } console.log(bar.next()); // { value: undefined, done: true }
- 生成器 + Promise解决异步的实现
function readFile (filaName) { return new Promise(function (resolve, reject) { fs.readFile(filename, function (err, data) { if (err) { reject(err) } else { resolve(data) } }) } function *read() { let template = yield readFile('./template.txt') let data = yield readFile('./data.txt') return { template: template, data: data } } // 生成迭代器r1 let r1 = read() let templatePromise = r1.next().value templatePromise.then(function(template) { // 将获取到的template的内容传递给生成器函数 let dataPromsie = r1.next(template).value dataPromise.then(function(data) { //最后一次执行next传入data的值;最后返回{template, data} let result = r1.next(data).value console.log(result) }) })
生成器 + promise的实现已经有了一些同步的样子; 借助一些工具( co ),可以优雅的编写上述的代码
//实现 co 方法 // 参数是一个生成器函数 function co (genFn) { let r1 = genFn() return new Promise(function(resolev, reject) { !function next(lastVal) { let p1 = r1.next(lastVal) if (p1.done) { resolve(p1.value) } else { p1.value.then(next, reject) } }() }) } 现在获取上边的result可以这样来取 co(read).then(function(result) { console.log(result) })
6、Async/await Async其实是一个语法糖,它的实现就是将Generator函数和自动执行器(co),包装在一个函数中
async function read() { let template = await readFile('./template.txt'); let data = await readFile('./data.txt'); return template + '+' + data; } // 等同于 function read(){ return co(function*() { let template = yield readFile('./template.txt'); let data = yield readFile('./data.txt'); return template + '+' + data; }); }
结论:异步编程发展的目标就是让异步逻辑的代码看起来像同步一样;发展到Async/await;是处理异步编程探索的一个里程碑。
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。