JavaScript异步编程演进:从回调到Promise与Generator

作者:半夏时间:2026-10-22 02:48:42浏览:564

本文深入解析JavaScript异步编程的三种核心模式。首先介绍基础的回调函数机制及其导致的回调地狱问题;接着阐述ES6中Promise规范如何优雅处理异步结果与错误;最后详解Generator生成器函数配合yield关键字,如何进一步简化异步流程控制,避免深层嵌套。

JavaScript异步编程概述

本文通过具体实例详细解析JavaScript中的异步编程技术,旨在为开发者提供参考与学习思路。

“异步函数”这一术语有时略显名不副实,因为常规调用中,程序必须等待函数返回结果后才会继续执行后续代码。在JavaScript语境下,若称某函数为“异步”,意指该函数会触发另一个函数的执行,且后者从事件队列中获取任务。若此后续函数作为参数传递给前者,则被称为“回调函数”。

回调函数机制与局限

回调函数

回调函数是JavaScript异步编程中最基础且常用的实现方式。

通过采用回调机制,同步操作被转化为异步操作。主函数无需阻塞程序运行,而是先执行核心逻辑,将耗时较长的操作推迟执行,从而提升程序响应效率。

回调函数的优势在于实现简单、逻辑直观且易于部署;然而,其缺点也显而易见,即随着嵌套层级增加,代码可读性与维护性显著下降。

以下定义了一个名为delay的异步函数,其作用是延迟指定函数的执行,从而允许当前正在运行的程序继续执行。delay函数的具体实现如下:


function delay(time, callback) {
 setTimeout(function () {
 callback("Slept for "+time);
 }, time);
}

若需连续执行两次delay操作,代码应如何编写?


delay(1000, function(msg) {
 console.log(msg);
 delay(1200, function (msg) {
 console.log(msg);
 }
})
//...waits 1000ms
// > "Slept for 1000"
//...waits another 1200ms
// > "Slept for 1200"

通过上述方式可确保两个delay操作按顺序依次执行。然而,若嵌套层级过多,便会形成所谓的“回调地狱”。当异步任务数量庞大时,维护大量嵌套的回调函数将变得极其困难。

Promise对象的应用

Promise

Promise是ES6标准中纳入的重要规范,目前各大主流框架均已实现了相关方法。

可以将Promise理解为一种“承诺”机制。若函数A调用函数B,B返回一个Promise对象给A,A即可据此制定执行计划:当B成功返回结果时,A执行方案1;若B未能返回预期结果(即拒绝),A则执行方案2。这种机制将所有潜在风险控制在A的可控范围内。

以下是ES6中Promise用法的示例代码:


'use strict';
var promiseCount = 0; 
function testPromise() {
 var thisPromiseCount = ++promiseCount;
 var log = document.getElementById('log');
 log.insertAdjacentHTML('beforeend', thisPromiseCount +') Started (<small>Sync code started</small>)<br/>');
 // 我们创建一个新的Promise,期望3秒后得到结果
 var p1 = new Promise(
 //当Promise解决或拒绝时该函数被调用
 function(resolve, reject) {
  log.insertAdjacentHTML('beforeend', thisPromiseCount +') Promise started (<small>Async code started</small>)<br/>');
  // 创建异步操作
  window.setTimeout(
  function() {
   // 满足Promise
   resolve(thisPromiseCount);
  }, Math.random() * 2000 + 1000);
  });
  // 当Promise被满足时执行
  p1.then(
  // 输出信息和值
  function(val) {
   log.insertAdjacentHTML('beforeend', val +') Promise fulfilled (<small>Async code terminated</small>)<br/>');
  })
  .catch(
  // 当Promise被拒绝时执行
   function(reason) {
   console.log('Handle rejected promise ('+reason+') here.');
   });
  log.insertAdjacentHTML('beforeend', thisPromiseCount +') Promise made (<small>Sync code terminated</small>)<br/>');
}

快速连续执行上述函数,可获得如下结果:

JavaScript异步编程概述

上述示例表明,1和2两个异步操作完成后,后续代码按正常顺序执行完毕。关于Promise的更多详细用法,可参考MDN文档。

许多JavaScript框架也提供了Promise相关方法,此处以jQuery为例进行说明。


$("button").bind( "click", function() {
 $("p").append( "Started...");
 $("div").each(function( i ) {
 $( this ).fadeIn().fadeOut( 1000 * (i+1) );
 });
 $( "div" ).promise().done(function() {
 $( "p" ).append( " Finished! " );
 });
});

从代码可见,当$("div")选择器关联的所有任务执行完毕后,将自动调用最后的done操作。

jQuery中的Promise同样可以代表多种执行结果,针对不同结果会分别调用相应的回调函数。以ajax调用为例:在1.5版本之前,代码必须编写为如下形式:


$.get('/getdata',{
 success:onSuccess,
 failure:onFailure,
 always:onAlways
});

而在1.5及更高版本中,由于引入了Promise对象,代码可简化为如下形式:


var promise = $.get('/getdata');
promise.done(onSuccess);
promise.fail(onFailure);
promise.always(onAlways);

这种变化的优势何在?为何必须在触发ajax调用后再附加回调?若ajax需实现多种效果,如触发动画、插入HTML、锁定输入等,仅由负责发出请求的应用代码处理所有这些效果显然不够优雅。通过仅传递Promise对象,代码结构显得更加简洁清晰。

更多详细内容请参考jQuery官方文档。

尽管Promise机制十分优雅,但它主要解决了回调地狱问题。真正简化JavaScript异步编程的,还是Generator(生成器)技术。

Generator生成器详解

Generator

生成器是ES6标准中引入的语法特性。

那么,生成器究竟是什么?让我们先观察以下代码:


function* quips(name) {
 yield "hello " + name + "!";
 yield "i hope you are enjoying the blog posts";
 if (name.startsWith("X")) {
 yield "it's cool how your name starts with X, " + name;
 }
 yield "see you later!";
}

没错,这就是JavaScript代码。它是否与你以往认知的JavaScript有所不同?此处的函数被称为“生成器函数”。

生成器函数与普通函数的区别主要体现在以下方面:

普通函数以function关键字开头,而生成器函数则以function*开头。

生成器函数中包含一个特殊关键字yield,其作用是暂停函数执行。配合next方法调用,可实现函数的逐步执行。

以下展示next方法的具体使用方式:


> var iter = quips("jorendorff");
 [object Generator]
> iter.next()
 { value: "hello jorendorff!", done: false }
> iter.next()
 { value: "i hope you are enjoying the blog posts", done: false }
> iter.next()
 { value: "see you later!", done: false }
> iter.next()
 { value: undefined, done: true }

可见,每次调用next方法后,生成器函数便会执行至下一个yield位置处。

接下来讲解如何利用生成器函数替代回调函数。仍以最初多次延迟delay操作为例。

首先,我们需要定义一个生成器:


function* myDelayedMessages() {
 /* delay 1000 ms and print the result */
 /* delay 1200 ms and print the result */
}

随后,我们需要设置delay时间以执行指定操作,此处暂定为空函数。


function* myDelayedMessages() {
 console.log(delay(1000, function(){}));
 console.log(delay(1200, function(){}));
}

接着,我们使用yield关键字:


function* myDelayedMessages() {
 console.log(yield delay(1000, function(){}));
 console.log(yield delay(1200, function(){}));
}

最后,我们需要指定一个名为run的函数来调用生成器函数的next方法,并将空函数替换为参数resume:


function run(generatorFunction) {
 var generatorItr = generatorFunction(resume);
 function resume(callbackValue) {
 generatorItr.next(callbackValue);
 }
 generatorItr.next()
}

最终执行代码如下:


run(function* myDelayedMessages(resume) {
 console.log(yield delay(1000, resume));
 console.log(yield delay(1200, resume));
})
//...waits 1000ms
// > "Slept for 1000"
//...waits 1200ms
// > "Slept for 1200"

通过上述方式,我们成功避免了回调地狱问题。

如需了解更多JavaScript相关内容,可查看本站专题:《javascript面向对象入门教程》、《JavaScript中json操作技巧总结》、《JavaScript切换特效与技巧总结》、《JavaScript查找算法技巧总结》、《JavaScript动画特效与技巧汇总》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》。

希望本文所述内容能对大家的JavaScript程序设计有所帮助。

作者最新文章
相关文章
更多