JavaScript Promise 对象详解:从异步回调到链式调用

作者:函数茶馆

时间:2026-10-22

浏览:133

本文详细介绍JavaScript中Promise对象的概念与应用。从单线程导致的异步需求出发,解析Promise如何分离执行与结果处理,涵盖链式调用、串行与并行异步任务执行(Promise.all/race),以及错误处理机制,帮助开发者掌握现代JS异步编程核心。

文章目录

JavaScript异步执行与回调函数

在JavaScript的运行环境中,代码执行机制为单线程模式。

受限于这一特性,JavaScript中涉及的网络请求及浏览器事件处理均必须采用异步执行方式。实现异步操作的一种常见手段是使用回调函数。


function callback() {
 console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒钟后调用callback函数
console.log('after setTimeout()');

分析上述代码的执行流程,在Chrome浏览器的控制台输出结果如下:

before setTimeout() after setTimeout() (等待1秒后) Done

由此可见,异步操作会在未来的某个时间点触发特定的函数调用。AJAX请求是典型的异步操作场景。参考前文代码示例:


request.onreadystatechange = function () {
 if (request.readyState === 4) {
  if (request.status === 200) {
   return success(request.responseText);
  } else {
   return fail(request.status);
  }
 }
}

在AJAX操作中直接嵌入success(request.responseText)和fail(request.status)回调函数虽可行,但代码可读性较差,且不利于后续复用。

是否存在更优的写法?例如采用如下形式:


var ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
 .ifFail(fail);

这种链式调用方式的优势在于,首先统一执行AJAX逻辑,暂不关注结果处理方式;随后根据执行成功或失败的状态,在未来的某个时刻分别调用success函数或fail函数。

Promise对象的概念与基础用法

正如古语所言“君子一诺千金”,这种“承诺未来将执行某操作”的对象,在JavaScript中被称为Promise对象。

目前存在多种开源Promise实现,ES6标准已将其统一规范,并由浏览器原生支持。建议先测试当前浏览器是否支持Promise:


'use strict';

new Promise(function () {});
// 直接运行测试:
alert('支持Promise!');

首先观察一个最简单的Promise示例:生成一个0到2之间的随机数,若该数小于1,则在等待一段时间后返回成功状态;否则返回失败状态:


function test(resolve, reject) {
 var timeOut = Math.random() * 2;
 log('set timeout to: ' + timeOut + ' seconds.');
 setTimeout(function () {
  if (timeOut < 1) {
   log('call resolve()...');
   resolve('200 OK');
  }
  else {
   log('call reject()...');
   reject('timeout in ' + timeOut + ' seconds.');
  }
 }, timeOut * 1000);
}

该test()函数接收两个参数,均为函数类型。若执行成功,将调用resolve('200 OK');若执行失败,则调用reject('timeout in ' + timeOut + ' seconds.')。可以看出,test()函数仅关注自身逻辑,不关心具体的resolve和reject如何处理最终结果。 定义好执行函数后,即可通过Promise对象来执行它,并在未来获取成功或失败的结果:


var p1 = new Promise(test);
var p2 = p1.then(function (result) {
 console.log('成功:' + result);
});
var p3 = p2.catch(function (reason) {
 console.log('失败:' + reason);
});

变量p1是一个Promise对象,负责执行test函数。由于test函数内部采用异步执行方式,当test函数执行成功时,需通知Promise对象:


// 如果成功,执行这个函数:
p1.then(function (result) {
 console.log('成功:' + result);
});

当test函数执行失败时,需通知Promise对象:


p2.catch(function (reason) {
 console.log('失败:' + reason);
});

由于Promise对象支持串联,上述代码可简化为:


new Promise(test).then(function (result) {
 console.log('成功:' + result);
}).catch(function (reason) {
 console.log('失败:' + reason);
});

进行实际测试,观察Promise的异步执行过程:


'use strict';

// 清除log:
var logging = document.getElementById('test-promise-log');
while (logging.children.length > 1) {
 logging.removeChild(logging.children[logging.children.length - 1]);
}

// 输出log到页面:
function log(s) {
 var p = document.createElement('p');
 p.innerHTML = s;
 logging.appendChild(p);
}
new Promise(function (resolve, reject) {
 log('start new Promise...');
 var timeOut = Math.random() * 2;
 log('set timeout to: ' + timeOut + ' seconds.');
 setTimeout(function () {
  if (timeOut < 1) {
   log('call resolve()...');
   resolve('200 OK');
  }
  else {
   log('call reject()...');
   reject('timeout in ' + timeOut + ' seconds.');
  }
 }, timeOut * 1000);
}).then(function (r) {
 log('Done: ' + r);
}).catch(function (reason) {
 log('Failed: ' + reason);
});

Log:

start new Promise... set timeout to: 0.5354042750614991 seconds. call resolve()... Done: 200 OK

由此可见,Promise的最大优势在于将异步执行的代码与处理结果的代码清晰分离:

JavaScript异步执行与回调函数

Promise的链式调用与串行处理

Promise还能实现更多功能,例如处理多个异步任务:先执行任务1,成功后再执行任务2,若任何任务失败则停止后续操作并执行错误处理函数。 若要串行执行此类异步任务,不使用Promise需编写多层嵌套代码。使用Promise后,只需简单编写:


job1.then(job2).then(job3).catch(handleError);

其中,job1、job2和job3均为Promise对象。 以下示例演示如何串行执行一系列需异步计算结果的任务:


'use strict';

var logging = document.getElementById('test-promise2-log');
while (logging.children.length > 1) {
 logging.removeChild(logging.children[logging.children.length - 1]);
}

function log(s) {
 var p = document.createElement('p');
 p.innerHTML = s;
 logging.appendChild(p);
}
// 0.5秒后返回input*input的计算结果:
function multiply(input) {
 return new Promise(function (resolve, reject) {
  log('calculating ' + input + ' x ' + input + '...');
  setTimeout(resolve, 500, input * input);
 });
}

// 0.5秒后返回input+input的计算结果:
function add(input) {
 return new Promise(function (resolve, reject) {
  log('calculating ' + input + ' + ' + input + '...');
  setTimeout(resolve, 500, input + input);
 });
}

var p = new Promise(function (resolve, reject) {
 log('start new Promise...');
 resolve(123);
});

p.then(multiply)
 .then(add)
 .then(multiply)
 .then(add)
 .then(function (result) {
 log('Got value: ' + result);
});

Log:

start new Promise... calculating 123 x 123... calculating 15129 + 15129... calculating 30258 x 30258... calculating 915546564 + 915546564... Got value: 1831093128

setTimeout可视为模拟网络请求等异步执行的函数。现将前文的AJAX异步执行函数转换为Promise对象,观察Promise如何简化异步处理:


'use strict';

// ajax函数将返回Promise对象:
function ajax(method, url, data) {
 var request = new XMLHttpRequest();
 return new Promise(function (resolve, reject) {
  request.onreadystatechange = function () {
   if (request.readyState === 4) {
    if (request.status === 200) {
     resolve(request.responseText);
    } else {
     reject(request.status);
    }
   }
  };
  request.open(method, url);
  request.send(data);
 });
}
var log = document.getElementById('test-promise-ajax-result');
var p = ajax('GET', '/api/categories');
p.then(function (text) { // 如果AJAX成功,获得响应内容
 log.innerText = text;
}).catch(function (status) { // 如果AJAX失败,获得响应代码
 log.innerText = 'ERROR: ' + status;
});

Promise的并行处理与错误容错

除串行执行多个异步任务外,Promise还支持并行执行异步任务。 假设一个页面聊天系统需从两个不同URL分别获取用户个人信息和好友列表,这两个任务可并行执行,使用Promise.all()实现如下:


var p1 = new Promise(function (resolve, reject) {
 setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
 setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
 console.log(results); // 获得一个Array: ['P1', 'P2']
});

某些场景下,多个异步任务旨在容错。例如,同时向两个URL读取用户个人信息,只需获取最先返回的结果。此时可使用Promise.race()实现:


var p1 = new Promise(function (resolve, reject) {
 setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
 setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
 console.log(result); // 'P1'
});

由于p1执行较快,Promise的then()将获取结果'P1'。p2虽继续执行,但其执行结果将被丢弃。

通过组合使用Promise,可将众多异步任务以并行和串行的方式组合执行。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章