1. 循环
要想循环执行一段代码,一般有两种方式,迭代或者递归。
(1)迭代方式
const f = () => {
for (let i = 0; i < 10; i++) {
console.log(i);
}
};
(2)递归方式
const f = i => {
if (i > 9) {
return;
}
console.log(i);
f(i + 1);
};
f(0);
以上是循环执行同步代码的常用方法。
但是要想循环执行包含异步操作的代码就不太容易了。
2. 异步
在包含异步代码时,如果使用for
循环,将无法保证顺序。
const f = () => {
for (let i = 0; i < 10; i++) {
setTimeout(() => console.log(i), Math.random() * 1000);
}
};
f();
以上代码由于会在随机时间后将任务加入事件队列,
所以,并不会保证顺序执行。
要想保证顺序,我们就必须手动将它们串起来,
让前面的异步代码执行完了之后,再执行后面的异步代码。
常用方法是使用递归,
const f = i => {
if (i > 9) {
return;
}
setTimeout(() => {
console.log(i);
f(i + 1);
}, Math.random() * 1000);
};
f(0);
3. 递归模式
以上递归函数有一个缺点,那就是递归函数的名字不能修改,
如果只修改了递归函数的名字,而没有修改对它的递归调用,就会出错。
虽然我们可以通过Y combinator来对匿名函数进行递归运算,
但实际用起来会比较繁琐,涉及的理论知识也不容易理解。
const y = k => (g => g(g))(f => n => k(f(f))(n));
const f = y(next => i => {
if (i > 9) {
return;
}
setTimeout(() => {
console.log(i);
next(i + 1);
}, Math.random() * 1000);
});
f(0);
有一个办法就是模仿Y combinator,
提取递归模式,构造一个高阶函数来完成递归,让接口更友好一些。
const recursion = (p0, fn) => fn(p0, p1 => recursion(p1, fn));
recursion(0, (i, next) => {
if (i > 9) {
return;
}
setTimeout(() => {
console.log(i);
next(i + 1);
}, Math.random() * 1000);
});
其中next
是一个函数,调用它会导致(i, next)=>{ }
重新被调用。
这样我们就不必担心递归函数的名字问题了,
而且recursion
还同样适用于包含异步代码的函数。
4. async function
ES 2017引入了async function,
它返回一个promise,并且还可以在其中await
一个promise。
const f = async () => {
console.log(1);
await new Promise(res => setTimeout(res, 1000));
console.log(2);
};
f();
这段代码会在打印1
之后等待1s,然后再打印2
。
(1)异步迭代
有了async function,我们就可以对异步代码进行迭代了,
const f = async () => {
for (let i = 0; i < 10; i++) {
await new Promise(res => setTimeout(() => {
console.log(i);
res();
}, Math.random() * 1000));
}
};
f();
(2)异步递归
同样对于异步代码,也就容易编写递归函数了。
const f = async i => {
if (i > 9) {
return;
}
await new Promise(res => setTimeout(() => {
console.log(i);
res();
}, Math.random() * 1000));
await f(i + 1);
};
f(0);
参考
ECMAScript 2017 Language Specification
MDN: async function
Y combinator
网友评论