7行代码实现异步函数调用库
发布在JavaScript译文2014年8月15日view:3367
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

在浏览器端,很多操作都是异步的,而我们经常采用回调的方式来实现异步调用。

举个例子,当前有两个方法,我们想逐个调用它们。它们操作同一个变量,第一个方法设定值,而第二个方法访问这个值。

var value;
var A = function() {
    setTimeout(function() {
        value = 10;
    }, 200);
}
var B = function() {
    console.log(value);
}

现在,我们执行A();B();后会在控制台中输出unefined,原因看上去是因为A方法异步设定了value值。我们可以通过回调的方法来解决这个问题:当设定取值之后再触发读取操作。

var value;
var A = function(callback) {
  setTimeout(function() {
    value = 10;
    callback();
  }, 200);
};
var B = function() {
  console.log(value);
};

A(function() {
  B();
});

这样是能实现功能,但是想象下:如果有五六个方法需要运行那会变成啥样子呢。不断的传递回调回事代码变得非常混乱,严重影响自己的心情。

解决的方法就是写一个帮助函数,它可以接受函数列表然后负责他们的运行。让我们看下简单的例子:

var queue = function(funcs) {
    // magic here
}

现在我们就调用上面的函数:queue([A, B]),它接收A和B作为参数。我们需要获得第一个函数并且执行它。

var queue = function(funcs) {
    var f = funcs.shift();
    f();
}

如果你运行上面的代码你会看到TypeError:undefined is not a function。这是因为A函数运行了一个不存在的回调了。那我们就传递个给它。

var queue = function(funcs) {
    var next = function() {
        // ...
    };
    var f = funcs.shift();
    f(next);
};

一旦A执行完成之后,就会执行next函数,而next函数绝对是调用下个函数的最好地方。现在让我稍微调整下代码,这样就可以一直执行数组中的所有函数。

var queue = function(funcs) {
    var next = function() {
        var f = funcs.shift();
        f(next);
    };
    next();
};

上面的代码看起来已经实现了我们的目标了。即A执行后执行B,打印了正确的变量value的值。这里起关键作用的是shift方法,它移除数组的第一个元素并返回给变量,这样逐渐的funcs数组会被清空,而这可能会导致一个错误。为了证实这个想法我们来做个试验。我们还是传递连个函数,但是我们不知道他们顺序,他们都可以接受回调并且执行回调。

var A = function(callback) {
    setTimeout(function() {
        value = 10;
        callback();
    }, 200);
};
var B = function(callback) {
    console.log(value);
    callback();
};

最终很明显我们获得了一个TypeError: undefined is not a function错误。我们可以通过检验funcs是否为空来阻止报错。

var queue = function(funcs) {
    (function next() {
        if(funcs.length > 0) {
            var f = funcs.shift();
            f(next);
        }
    })();
};

这里还有一个改动就是在定义next后就立即执行了,这样可以节省一些代码。

下面让我们再考虑的更全面一点。当前执行的函数的scope是啥呢。函数中的this关键指向的可能是全局window对象。如果我们能自己设定scope就酷了。

var queue = function(funcs, scope) {
    (function next() {
          if(funcs.length > 0) {
              var f = funcs.shift();
              f.apply(scope, [next]);
          }
    })();
};

我们在方法中增加了一个参数。同时我们不再使用f(next)而是使用apply,这样可以设定scope了。

最后一个特性就是在函数中传递参数的能力。当然我们并不知道它可能传递多少个参数,这就是我们使用arguments变量的原因。它看起来像数组,但其实不是的。但是在apply方法中需要使用数组作为参数,所以我们做下变动。

var queue = function(funcs, scope) {
    (function next() {
          if(funcs.length > 0) {
              var f = funcs.shift();
              f.apply(scope, [next].concat(Array.prototype.slice.call(arguments, 0)));
          }
    })();
};

下面是一个演示这个库的完整例子。

var obj = {
    value: null
};

queue([
    function(callback) {
        var self = this;
        setTimeout(function() {
            self.value = 10;
            callback(20);
        }, 200);
    },
    function(callback, add) {
        console.log(this.value + add);
        callback();
},
    function() {
        console.log(obj.value);
    }
], obj);

你可以看到如下输出:

30 10

正如标题所讲,我们只用了七行代码:

var queue = function(funcs, scope) {
    (function next() {
          if(funcs.length > 0) {
              funcs.shift().apply(scope || {}, [next].concat(Array.prototype.slice.call(arguments, 0)));
          }
    })();
};

代码都可以从github中获得到。

编辑:github 本文参考自7 lines JavaScript library for calling asynchronous functions

评论
发表评论
6年前

强大,good

6年前

@jnotnull 靠,这编辑器,还不能删评论。 应该是这样scope.shift()

6年前

@jnotnull 请忽略多余的 和 ‘ 符号 :)

6年前

@jnotnull 是否可以这样,scope为context数组:

var queue = function(funcs, scope) {
    (function next() {
          if(funcs.length > 0) {
              funcs.shift().apply(<b>`scope.shift()`</b> || {}, [next].concat(Array.prototype.slice.call(arguments, 0)));
          }
    })();
};
6年前

@YuC_C 还请把好的方案贴出来 感谢

6年前

@kevin 如果不知杨,如何解决setTimeout的之行顺序问题呢,是否有好的方案

6年前

@有逗气 那该如何解决setTimout的之行问题呢,能否给个方案

6年前

@景在峰中 哪些地方不易懂,还请列一下,方便改进

6年前

感觉传入的参数中还必须明确写入callback 函数啊?有点鸡肋啊!

6年前

this被指定为一个,这样不好

6年前

就是一个把下一个待执行的function放入上一个function的参数作为回调,弄了半天还得上一个function主动调用回调,一点意义都没有。尤其this指向还被串到一起,很容易污染变量

6年前

你翻译的时候 可以再解释易通点,这样方便 新手理解

WRITTEN BY
jnotnull
专注于前端开发
TA的新浪微博
PUBLISHED IN
JavaScript译文

翻译前端文章

我的收藏