当谈到与处理异步开发在JavaScript中有很多工具可以使用。这篇文章解释了这些工具中的四个和它们的优点。这些是回调,监听器,控制流库和承诺。

示例场景

为了说明这四个工具的使用,让我们创建一个简单的示例场景。

让我们说,我们想要找到一些记录,然后处理它们,最后返回处理结果。两个操作(查找和处理)是异步的。

回调

让我们从回调模式开始,这是最基本的和最好的已知模式来处理异步编程。

回调看起来像这样:

finder([1, 2], function(results) {
  ..do something
});

在回调模式中,我们调用一个将执行异步操作的函数。我们传递的参数之一是当操作完成时将被调用的函数。

建立

为了说明它们如何工作,我们需要一些能找到并处理记录的函数。在现实世界中,这些函数会产生一个AJAX请求并返回结果,但是现在我们只使用超时。

function finder(records, cb) {
    setTimeout(function () {
        records.push(3, 4);
        cb(records);
    }, 1000);
}
function processor(records, cb) {
    setTimeout(function () {
        records.push(5, 6);
        cb(records);
    }, 1000);
}

使用回调

使用这些函数的代码如下所示:

finder([1, 2], function (records) {
    processor(records, function(records) {
      console.log(records);
    });
});

我们调用第一个函数,传递一个回调。在这个回调中,我们调用第二个函数传递另一个回调。

通过传递对另一个函数的引用,可以更清楚地写这些嵌套回调。

function onProcessorDone(records){
  console.log(records);
}

function onFinderDone(records) {
    processor(records, onProcessorDone);
}

finder([1, 2], onFinderDone);

在这两种情况下,控制台日志上面用log [1,2,3,4,5,6]

工作示例:

// setup

function finder(records, cb) {
    setTimeout(function () {
        records.push(3, 4);
        cb(records);
    }, 500);
}
function processor(records, cb) {
    setTimeout(function () {
        records.push(5, 6);
        cb(records);
    }, 500);
}

// using the callbacks
finder([1, 2], function (records) {
    processor(records, function(records) {
             console.log(records);       
    });
});

// or

function onProcessorDone(records){
    alert(records);   
}

function onFinderDone(records) {
    processor(records, onProcessorDone);
}

finder([1, 2], onFinderDone);

优点

他们是一个非常熟悉的模式,所以他们熟悉,因此容易理解。 很容易在你自己的库/函数中实现。

缺点

嵌套回调将形成如上所示的臭名昭着的末日金字塔,当你有多个嵌套层时,这很难阅读。但这是很容易修复通过拆分功能也如上所示。 您只能为给定事件传递一个回调,在许多情况下,这可能是一个很大的限制。

听众

监听器也是一个众所周知的模式,大多被jQuery和其他DOM库流行。侦听器可能如下所示:

finder.on('done', function (event, records) {
  ..do something
});

我们在添加监听器的对象上调用一个函数。在那个函数中,我们传递我们想要监听的事件的名字和一个回调函数。'on'是这个函数的常用名称之一,你会遇到的其他常用名称是'bind','listen','addEventListener','observe'。

建立

让我们为一个监听器演示做一些设置。不幸的是,所需的设置比在回调示例中多一点。

首先,我们需要一些对象来完成查找和处理记录的工作。

var finder = {
    run: function (records) {
        var self = this;
        setTimeout(function () {
            records.push(3, 4);
           self.trigger('done', [records]);
        }, 1000);
    }
}
var processor = {
    run: function (records) {
        var self = this;
        setTimeout(function () {
            records.push(5, 6);
            self.trigger('done', [records]);
        }, 1000);
    }
}

注意,当工作完成时,他们正在调用方法触发器,我将使用mix-in将这个方法添加到这些对象。再次“触发”是你会遇到的名称之一,其他常用的名字是'火'和'发布'。

我们需要一个具有侦听器行为的mix-in对象,在这种情况下,我将仅仅依靠jQuery:

var eventable = {
    on: function(event, cb) {
        $(this).on(event, cb);
    },
    trigger: function (event, args) {
        $(this).trigger(event, args);
    }
}

然后将行为应用于我们的finder和处理器对象:

$.extend(finder, eventable);
$.extend(processor, eventable);

优秀,现在我们的对象可以接听听者和触发事件。

使用侦听器

使用侦听器的代码很简单:

finder.on('done', function (event, records) {
  processor.run(records);
});
processor.on('done', function (event, records) {
    console.log(records);
});
finder.run([1,2]);

同样,控制台运行将输出[1,2,3,4,5,6]

工作示例:

// using listeners
var eventable = {
    on: function(event, cb) {
        $(this).on(event, cb);
    },
    trigger: function (event, args) {
        $(this).trigger(event, args);
    }
}

var finder = {
    run: function (records) {
            var self = this;
        setTimeout(function () {
            records.push(3, 4);
           self.trigger('done', [records]);            
        }, 500);
    }
}
var processor = {
    run: function (records) {
         var self = this;
        setTimeout(function () {
            records.push(5, 6);
            self.trigger('done', [records]);            
        }, 500);
    }
 }
 $.extend(finder, eventable);
 $.extend(processor, eventable);

finder.on('done', function (event, records) {
          processor.run(records);  
    });
processor.on('done', function (event, records) {
    alert(records);
});
finder.run([1,2]);

优点

这是另一个很好理解的模式。 最大的优点是你不限于每个对象一个监听器,你可以添加任意多的监听器。例如

finder
  .on('done', function (event, records) {
      .. do something
  })
  .on('done', function (event, records) {
      .. do something else
  });

缺点

比你自己的代码中的回调更难设置,你可能想使用一个库,例如jQuery,bean.js。

流控制库

流控制库也是一个非常好的方式来处理异步代码。我特别喜欢的是Async.js。

使用Async.js的代码看起来像这样:

async.series([
    function(){ ... },
    function(){ ... }
]);

设置(示例1)

再次,我们需要一些功能来完成这项工作,因为在其他示例中,这些功能在现实世界中可能会创建一个Ajax请求并返回结果。现在让我们使用超时。

function finder(records, cb) {
    setTimeout(function () {
        records.push(3, 4);
        cb(null, records);
    }, 1000);
}
function processor(records, cb) {
    setTimeout(function () {
        records.push(5, 6);
        cb(null, records);
    }, 1000);
}

节点继续传递风格

注意在上面的函数里面的回调中使用的样式。

1 cb(null, records); 如果没有发生错误,回调中的第一个参数为null; 或错误(如果发生)。这是Node.js库中的常见模式,Async.js使用此模式。通过使用这种风格,Async.js和回调之间的流变得超级简单。

使用异步

将消耗这些函数的代码如下所示:

async.waterfall([
    function(cb){
        finder([1, 2], cb);
    },
    processor,
    function(records, cb) {
        alert(records);
    }
]);

Async.js负责在前一个函数完成后按顺序调用每个函数。注意我们如何才能传递“处理器”函数,这是因为我们使用的是Node继承风格。正如你可以看到,这个代码是很小,很容易理解。

工作示例:

// setup
function finder(records, cb) {
    setTimeout(function () {
        records.push(3, 4);
        cb(null, records);
    }, 500);
}
function processor(records, cb) {
    setTimeout(function () {
        records.push(5, 6);
        cb(null, records);
    }, 500);
}

async.waterfall([
    function(cb){
        finder([1, 2], cb);
    },
    processor
    ,
    function(records, cb) {
        alert(records);
    }
]);

另一种设置(实施例2)

现在,当进行前端开发时,不太可能有一个跟随回调(null,results)签名的库。所以更现实的例子看起来像这样:

function finder(records, cb) {
    setTimeout(function () {
        records.push(3, 4);
        cb(records);
    }, 500);
}
function processor(records, cb) {
    setTimeout(function () {
        records.push(5, 6);
        cb(records);
    }, 500);
}

// using the finder and the processor
async.waterfall([
    function(cb){
        finder([1, 2], function(records) {
            cb(null, records)
        });
    },
    function(records, cb){
        processor(records, function(records) {
            cb(null, records);
        });
    },
    function(records, cb) {
        alert(records);
    }
]);

它变得更加复杂,但至少你可以看到流从上到下。

工作示例:

// setup

function finder(records, cb) {
    setTimeout(function () {
        records.push(3, 4);
        cb(records);
    }, 500);
}
function processor(records, cb) {
    setTimeout(function () {
        records.push(5, 6);
        cb(records);
    }, 500);
}

async.waterfall([
    function(cb){
        finder([1, 2], function(records) {
            cb(null, records)
        });
    },
    function(records, cb){
        processor(records, function(records) {
            cb(null, records);
        });
    },
    function(records, cb) {
        alert(records);
    }
]);

优点

通常使用控制流库的代码更容易理解,因为它遵循自然顺序(从上到下)。这不是回调和侦听器。 缺点

如果函数的签名不匹配,如第二个例子,那么你可以认为流控制库提供很少的可读性。

承诺

最后,我们到达我们的最终目的地。承诺是一个非常强大的工具,但他们是最不了解的。

使用promise的代码可能如下所示:

finder([1,2])
    .then(function(records) {
      .. do something
    });

这将有很大的不同取决于你使用的承诺库,在这种情况下,我使用when.js。

建立

out finder和处理器功能如下所示:

function finder(records){
    var deferred = when.defer();
    setTimeout(function () {
        records.push(3, 4);
        deferred.resolve(records);
    }, 500);
    return deferred.promise;
}
function processor(records) {
     var deferred = when.defer();
    setTimeout(function () {
        records.push(5, 6);
        deferred.resolve(records);
    }, 500);
    return deferred.promise;
}

每个函数创建一个延迟对象并返回一个promise。然后当结果到达时解决延迟。

使用promises

使用这些函数的代码如下所示:

finder([1,2])
    .then(processor)
    .then(function(records) {
            alert(records);
    });

正如你可以看到,它是相当最小,很容易理解。当这样使用时,promise为你的代码带来了很多清晰,因为他们遵循自然的流程。注意在第一个回调中我们可以简单的传递'处理器'函数。这是因为这个函数返回一个promise本身,所以一切都只是流畅地。

工作示例:

// using promises
function finder(records){
    var deferred = when.defer();
    setTimeout(function () {
        records.push(3, 4);
        deferred.resolve(records);
    }, 500);
    return deferred.promise;
}
function processor(records) {
     var deferred = when.defer();
    setTimeout(function () {
        records.push(5, 6);
        deferred.resolve(records);
    }, 500);
    return deferred.promise;
}

finder([1,2])
    .then(processor)
    .then(function(records) {
            alert(records);
    });

有很多承诺:

它们可以作为常规对象传递 聚合成更大的承诺 你可以为失败的promise添加处理程序

承诺的大好处

现在如果你认为这是所有的承诺你失去了我认为最大的优势。Promises有一个巧妙的技巧,回调,侦听器或控制流都不能做。你可以添加一个监听器来承诺,即使它已经解决,在这种情况下,监听器将立即触发,这意味着你不必担心,如果事件已经发生,当你添加监听器。这对于聚合promises工作相同。让我告诉你一个例子:

function log(msg) {
    document.write(msg + '<br />');
}

// using promises
function finder(records){
    var deferred = when.defer();
    setTimeout(function () {
        records.push(3, 4);
        log('records found - resolving promise');
        deferred.resolve(records);
    }, 100);
    return deferred.promise;
}

var promise = finder([1,2]);

// wait 
setTimeout(function () {
    // when this is called the finder promise has already been resolved
    promise.then(function (records) {
        log('records received');        
    });
}, 1500);

这是处理浏览器中的用户交互的巨大功能。在复杂应用程序中,您现在可能不会执行用户将采取的操作顺序,因此您可以使用promise来跟踪使用交互。如果有兴趣,看看这个其他职位。

优点

真的很强大,你可以聚合承诺,传递它们,或者在已经解决时添加监听器。 缺点

最不了解的所有这些工具。 他们可能很难跟踪,当你有很多聚合承诺与一路上添加的听众。

结论

而已!在我看来,这是处理异步代码的四个主要工具。希望我帮助你更好地了解他们,并为您提供更多的选择异步需求。

results matching ""

    No results matching ""