【前端面试】 JS异步编程
2018-02-04 15:44
113 查看
callback回调方法
function fn1 () { console.log('Function 1') } function fn2 () { setTimeout(() => { console.log('Function 2') }, 500) } function fn3 () { console.log('Function 3') } fn1() fn2() fn3() //输出 fn1 fn3 fn2
其中fn2可以视作一个延迟了500毫秒执行的异步函数。现在我希望可以依次执行fn1,fn2,fn3。为了保证fn3在最后执行,我们可以把它作为fn2的回调函数:
function fn2 (f) { setTimeout(() => { console.log('Function 2') f() }, 500) } fn2(fn3)
通过传入一个回调函数作为参数来实现。
事件发布/订阅
主旨就是定义一个数组保存function,通过在各个function中调用内部方法来一个个抛出事件并执行。class funArray { constructor(...arr) { this.funArr = [...arr] } next() { let fun = this.funArr.shift() if (typeof fun === 'function') fun() } run() { this.next() } } function fn1() { console.log('Function 1') myevents.next() } function fn2() { setTimeout(() => { console.log('Function 2') myevents.next() }, 500) } function fn3() { console.log('Function 3') myevents.next() } var myevents = new funArray(fn1, fn2, fn3) myevents.run()
promise 链式 (ES6)
promise 的声明周期包括三个部分:- pendin(进行中)
- rejected(拒绝)
- fulfilled(成功)
存在两种情况,从pending => rejected 或者 从pending =>fulfilled
function fn1() { console.log('Function 1') } function fn2() { return new Promise((resolve, reject) => { setTimeout(() => { console.log('Function 2') resolve() }, 500) }) } function fn3() { console.log('Function 3') } fn1() fn2().then(function(){ //成功 f3() },function(){ //失败 })
使用Promise与回调有两个最大的不同,第一个是fn2与fn3得以解耦;第二是把函数嵌套改为了链式调用,无论从语义还是写法上都对开发者更加友好。
generator
如果说Promise的使用能够化回调为链式,那么generator的办法则可以消灭那一大堆的Promise特征方法,比如一大堆的then()。function fn1 () { console.log('Function 1') } function fn2 () { setTimeout(() => { console.log('Function 2') af.next() }, 500) } function fn3 () { console.log('Function 3') } 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
在这个例子中,generator函数asyncFunArr()接受一个待执行函数列表fn,异步函数将会通过yield来执行。在异步函数内,通过af.next()激活generator函数的下一步操作。
这么粗略的看起来,其实和发布/订阅模式非常相似,都是通过在异步函数内部主动调用方法,告诉订阅者去执行下一步操作。但是这种方式还是不够优雅,比如说如果有多个异步函数,那么这个generator函数肯定得改写,而且在语义化的程度来说也有一点不太直观。
async await 方法
ES2017 标准引入了 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 gen = function* () { const f1 = yield readFile('/etc/fstab'); const f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };
写成async函数,就是下面这样。
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函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
asyncReadFile();
上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。
(2)更好的语义。
async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
(4)返回值是 Promise。
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
相关文章推荐
- Web前端面试指导(三十八):js延迟加载的方式有哪些?
- 前端面试送命题(一)-JS三座大山
- web前端面试中常见的js基础又实用的知识回顾
- 前端面试js小总结(一)
- 前端面试 JS 篇
- 前端web优化和js常见面试内容整理
- 前端面试之js相关问题(一)
- Web前端面试指导(二十二):用js实现千位分隔符,怎么实现?
- 前端面试准备1----JS中eval()解析和为什么不要使用eval
- 前端面试:js的继承实现
- 前端面试js(语法)
- 前端面试集锦(三)js中的作用域
- 最新前端面试- js
- 【前端知识点】node.js的特点通俗解读面试必备-单线程高并发、异步io、跨平台
- 前端js面试技巧(1)——js基础部分
- Web前端面试笔试题2——JS(1):函数调用(局部变量/全局变量)
- 前端面试 - js隐式类型转换特殊实例
- js前端面试总结题汇总积累
- 前端面试之JS
- 前端周刊第47期:Vue.js 后台模板 + React Conf 2017 + 饿了么面试宝典