JavaScript

[JavaScript] 异步循环模式

2017-12-14  本文已影响38人  何幻

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

上一篇 下一篇

猜你喜欢

热点阅读