您的位置:首页 > 职场人生

【前端面试】 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命令的语法糖。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  前端 异步 js javascript