JavaScript Promise 核心概念与用法详解

作者:代码拾荒

时间:2026-10-22

浏览:370

本文深入解析 JavaScript 中 Promise 的核心机制,涵盖其定义、引入背景及三大状态(Pending、Fulfilled、Rejected)。详细讲解 Promise 的创建方式、执行流程、链式调用原理以及参数传递机制,并逐一剖析 catch、all、race、resolve 和 reject 等常用 API 的实际用法与注意事项。

文章目录

约定与术语说明

约定

  1. 本文中的演示代码部分为伪代码,无法直接运行。
  2. 除非另有说明,本文所有示例均基于 ES6 规范。
  3. Object.method 表示静态方法,Object#method 表示实例方法。例如,Promise#then 是 Promise 的实例方法,而 Promise.resolve 则是 Promise 的静态方法。

Promise 的概念与背景

2、什么是 Promise?

首先,我们需要理解 Promise 的本质。

Promise 是一个抽象的异步处理对象及其相关操作组件。为了更直观地理解,可以将 Promise 视为一个容器,它封装了一个未来才会结束的事件结果,通常指代一个异步操作。

Promise 的概念最早在 E 语言中提出,该语言基于并行处理设计。JavaScript 在 ES6 之后开始支持 Promise 特性,旨在解决异步操作的问题。此处简要说明 ES6:ECMAScript 是 JavaScript 的国际标准,JavaScript 是其实现之一,而 ES6(全称 ECMAScript 6)是该标准的一个版本。

引入 Promise 的原因

3、Javascript 为什么要引入 Promise?

前文提到,JavaScript 支持 Promise 是为了解决异步操作问题。你可能会问,JavaScript 不是可以通过回调函数处理异步操作吗?这是因为 Promise 是一种更强大的异步处理方式,它拥有统一的 API 和规范。下面我们将对比传统回调函数处理与 Promise 处理异步操作的差异。

使用回调函数处理异步操作:


login("http://www.r9it.com/login.php", function(error, result){
  // 登录失败处理
 if(error){
    throw error;
  }
  // 登录成功时处理
});

Node.js 等环境规定,JavaScript 回调函数的第一个参数为 Error 对象,这是一种惯例。虽然统一参数使用规则可以使写法更清晰,但这仅是编码规约,采用不同写法也不会报错。而 Promise 则将类似的异步处理对象和规则进行了规范化,采用统一接口编写,偏离规定方法的写法会导致错误。

使用 Promise 处理异步操作:


var promise = loginByPromise("http://www.r9it.com/login.php"); 
promise.then(function(result){
  // 登录成功时处理
}).catch(function(error){
  // 登录失败时处理
});

通过上述两个示例可以看出,使用 Promise 对象可以将异步操作以同步操作的流程表达出来。这样在处理多个异步操作时,可以避免层层嵌套的回调函数(后续会有演示)。此外,Promise 对象提供了统一的接口,必须通过调用 Promise#then 和 Promise#catch 这两个方法来获取结果,其他方法均不可用,从而使异步处理操作更加简便。

Promise 的基本用法与执行流程

4、基本用法

在 ES6 中,可以使用三种方法创建 Promise 实例(对象)。

(1). 构造方法


let promies = new Promise((resolve, reject) => {
 resolve(); //异步处理 
});

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。它们是两个由 JavaScript 引擎提供的函数,无需自行部署。

(2). 通过 Promise 实例的方法,Promise#then 方法返回的也是一个 Promise 对象


promise.then(onFulfilled, onRejected);

(3). 通过 Promise 的静态方法,Promise.resolve(),Promise.reject()


var p = Promise.resolve();
p.then(function(value) {
 console.log(value); 
});

4.1 Promise 的执行流程

  1. new Promise 构造器执行后,会返回一个 promise 对象;
  2. 为 promise 注册一个处理结果的回调函数(resolved)和一个异常处理函数(rejected);

4.2 Promise 的状态

实例化的 Promise 有三个状态:

Fulfilled: has-resolved,表示成功解决,此时会调用 onFulfilled。

Rejected: has-rejected,表示解决失败,此时会调用 onRejected。

Pending: unresolve,表示待解决,即既不是 resolve 也不是 reject 的状态。这是 promise 对象刚被创建后的初始化状态。

前文提到,Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。

resolve 函数的作用是将 Promise 对象的状态从 未处理 变成 处理成功 (unresolved => resolved),在异步操作成功时调用,并将异步操作的结果作为参数传递出去。

reject 函数的作用是将 Promise 对象的状态从 未处理 变成 处理失败 (unresolved => rejected),在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。

Promise 实例生成后,可以使用 then 方法和 catch 方法分别指定 resolved 状态和 rejected 状态的回调函数。

以下是 Promise 的状态图

约定与术语说明

Promise 的基本特性

4.3 Promise 的基本特性

【1】 对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,其英语意思即为“承诺”,表示其他手段无法改变。

【2】 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

例如以下代码,reject 方法是无效的


var promise = new Promise((fuck, reject) => {
 resolve("xxxxx");
 //下面这行代码无效,因为前面 resolve 方法已经将 Promise 的状态改为 resolved 了
 reject(new Error()); 
});

promise.then((value) => { 
 console.log(value);
})

下图是 Promise 的状态处理流程图

约定与术语说明

Promise 的执行顺序

5、Promise 的执行顺序

我们知道 Promise 在创建的时候是立即执行的,但事实证明 Promise 只能执行异步操作,即使在创建 Promise 时就立即改变其状态。


var p = new Promise((resolve, reject) => {
 console.log("start Promise");
 resolve("resolved"); 
});

p.then((value) => {
 console.log(value);
}) 

console.log("end Promise");

打印的结果是:

start Promise end Promise resolved

或许你会问,这个操作明明是同步的,定义 Promise 里面的代码都被立即执行了,那么回调应该紧接着 resolve 函数执行,那么应该先打印 “resolved” 而不应该先打印 “end Promise”。

这是 Promise 规范规定的,为了防止同步调用和异步调用同时存在导致的混乱。

Promise 的链式调用与参数传递

6、Promise 的链式调用(连贯操作)

前文讲过,Promise 的 then 方法以及 catch 方法返回的都是新的 Promise 对象,这样我们可以非常方便地解决嵌套的回调函数问题,也可以很方便地实现流程任务。


var p = new Promise(function(resolve, reject) {
  resolve();
});
function taskA() {
  console.log("Task A");
}
function taskB() {
  console.log("Task B");
}
function taskC() {
  console.log("Task C");
}
p.then(taskA())
.then(taskB())
.then(taskC())
.catch(function(error) {
  console.log(error);
});

上面这段代码很方便地实现了从 taskA 到 taskC 的有序执行。

当然你可以把 taskA - taskC 换成任何异步操作,如从后台获取数据:


var getJSON = function(url, param) {
 var promise = new Promise(function(resolve, reject){
 var request = require('ajax-request');
 request({url:url, data: param}, function(err, res, body) {
  if (!err && res.statusCode == 200) {
  resolve(body);
  } else {
  reject(new Error(err));
  }
 });
 });

 return promise;
};
var url = "login.php";
getJSON(url, {id:1}).then(result => {
 console.log(result);
 return getJSON(url, {id:2})
}).then(result => {
 console.log(result);
 return getJSON(url, {id:3});
}).then(result => {
 console.log(result);
}).catch(error => console.log(error));

这样用起来似乎很爽,但是有个问题需要注意,我们说过每个 then() 方法都返回一个新的 Promise 对象,那既然是 Promise 对象,那肯定就有注册 onFulfilled 和 onRejected,如果某个任务流程的 then() 方法链过长的话,前面的任务抛出异常,会导致后面的任务被跳过。


function taskA() {
  console.log("Task A");
  throw new Error("throw Error @ Task A");
}
function taskB() {
  console.log("Task B");
}
function onRejected(error) {
  console.log(error);
}
function finalTask() {
  console.log("Final Task");
}
var promise = Promise.resolve();
promise
  .then(taskA)
  .then(taskB)
  .catch(onRejected)
  .then(finalTask);

执行的结果是:

Task A Error: throw Error @ Task A Final Task

显然,由于 A 任务抛出异常(执行失败),导致 .then(taskB) 被跳过,直接进入 .catch 异常处理环节。

6.1 promise chain 中如何传递参数

前文简单阐述了 Promise 的链式调用,能够非常有效地处理异步的流程任务。

但在实际的使用场景中,任务之间通常都是有关联的,比如 taskB 需要依赖 taskA 的处理结果来执行,这有点类似 Linux 管道机制。Promise 中处理这个问题也很简单,那就是在 taskA 中 return 的返回值,会在 taskB 执行时传给它。


function taskA() {
  console.log("Task A");
  return "From Task A";
}
function taskB(value) {
 console.log(value);
  console.log("Task B");
  return "From Task B";
}
function onRejected(error) {
  console.log(error);
}
function finalTask(value) {
 console.log(value);
  console.log("Final Task");
}
var promise = Promise.resolve();
promise
  .then(taskA)
  .then(taskB)
  .catch(onRejected)
  .then(finalTask);

搞定,就这么简单!

6.2 resolve 和 reject 参数

reject 函数的参数通常是 Error 对象的实例,表示抛出的错误;resolve 函数的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像上面的 getJSON() 方法一样。


var p1 = new Promise(function (resolve, reject) {
 setTimeout(() => reject(new Error('fail')), 3000)
})

var p2 = new Promise(function (resolve, reject) {
 setTimeout(() => resolve(p1), 1000)
})

p2
 .then(result => console.log(result))
 .catch(error => console.log(error))

注意,这时 p1 的状态就会传递给 p2,也就是说,p1 的状态决定了 p2 的状态。

如果 p1 的状态是 pending,那么 p2 的回调函数就会等待 p1 的状态改变;

如果 p1 的状态已经是 resolved 或者 rejected,那么 p2 的回调函数将会立刻执行。

Promise 常用 API 详解

7、Promise 基本方法

ES6 的 Promise API 提供的方法不是很多,下面介绍一下 Promise 对象常用的几个方法。

7.1 Promise.prototype.catch()

Promise.prototype.catch 方法是 .then(null, rejection) 的别名,用于指定发生错误时的回调函数。


p.then((val) => console.log('fulfilled:', val))
 .catch((err) => console.log('rejected', err));

// 等同于
p.then((val) => console.log('fulfilled:', val))
 .then(null, (err) => console.log("rejected:", err));

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch 语句捕获。所以通常建议使用 catch 方法去捕获异常,而不要用 then(null, function(error) {}) 的方式,因为这样只能捕获当前 Promise 的异常。


p.then(result => {console.log(result)})
 .then(result => {console.log(result)})
 .then(result => {console.log(result)})
 .catch(error => {
  //捕获上面三个 Promise 对象产生的异常
  console.log(error);
 });

跟传统的 try/catch 代码块不同的是,如果没有使用 catch 方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。

通俗的说法就是“Promise 会吃掉错误”。

比如下面的代码就出现这种情况


var p = new Promise(function(resolve, reject) {
  // 下面一行会报错,因为x没有声明
  resolve(x + 2);
 });
p.then(() => {console.log("every thing is ok.");});
// 这行代码会正常执行,不会受 Promise 里面报错的影响
console.log("Ok, it's Great.");

7.2 Promise.all()

Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。用来处理组合 Promise 的逻辑操作。


var p = Promise.all([p1, p2, p3]); 

上面代码 p 的状态由 p1、p2、p3 决定,分成两种情况。

  1. 只有 p1、p2、p3 的状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数。
  2. 只要 p1、p2、p3 之中有一个被 rejected,p 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数。

下面是一个具体的例子


// 生成一个Promise对象的数组
var promises = [1,2,3,4,5,6].map(function (id) {
 return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(function (posts) {
 // ...
}).catch(function(reason){
 // ...
});

上面代码中,promises 是包含 6 个 Promise 实例的数组,只有这 6 个实例的状态都变成 fulfilled,或者其中有一个变为 rejected,才会调用 Promise.all 方法后面的回调函数。

7.3 Promise.race()

Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。与 Promise.all 不同的是,只要有一个 promise 对象进入 FulFilled 或者 Rejected 状态的话,Promise.race 就会继续进行后面的处理。


var p = Promise.race([p1, p2, p3]);

上面代码中,只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。Promise.race 方法的参数与 Promise.all 方法一样,如果不是 Promise 实例,就会先调用 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。

下面是一个具体的例子


// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(delay);
    }, delay);
  });
}
// 任何一个promise变为resolve或reject 的话程序就停止运行
Promise.race([
  timerPromisefy(1),
  timerPromisefy(32),
  timerPromisefy(64),
  timerPromisefy(128)
]).then(function (value) {
  console.log(value);  // => 1
});

7.4 Promise.resolve()

Promise.resolve 方法有 2 个作用,一个就是前文说的,它是通过静态方法创建 Promise 实例的渠道之一,另一个作用就是将 Thenable 对象转换为 Promise 对象。

那么什么是 Thenable 对象呢?ES6 Promises 里提到了 Thenable 这个概念,简单来说它就是一个非常类似 promise 的东西。就像我们有时称具有 .length 方法的非数组对象为 Array like 一样,Thenable 指的是一个具有 .then 方法的对象。

这种将 Thenable 对象转换为 Promise 对象的机制要求 thenable 对象所拥有的 then 方法应该和 Promise 所拥有的 then 方法具有同样的功能和处理过程,在将 Thenable 对象转换为 Promise 对象的时候,还会巧妙地利用 Thenable 对象原来具有的 then 方法。

到底什么样的对象能算是 Thenable 的呢,最简单的例子就是 jQuery.ajax(),它的返回值就是 Thenable 的。

将 Thenable 对象转换为 Promise 对象


var promise = Promise.resolve($.ajax('/json/comment.json'));// => promise对象
promise.then(function(value){
  console.log(value);
});

除了上面的方法之外,Promise.resolve 方法的参数还有以下三种情况。

(1). 参数是一个 Promise 实例

如果参数是 Promise 实例,那么 Promise.resolve 将不做任何修改、原封不动地返回这个实例。

(2). 参数不是具有 then 方法的对象,或根本就不是对象

如果参数是一个原始值,或者是一个不具有 then 方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 resolved。


var p = Promise.resolve('Hello');
p.then(function (s){
 console.log(s)
});

上面代码生成一个新的 Promise 对象的实例 p。由于字符串 Hello 不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是 resolved,所以回调函数会立即执行。Promise.resolve 方法的参数,会同时传给回调函数。

(3). 不带有任何参数

Promise.resolve 方法允许调用时不带参数,直接返回一个 resolved 状态的 Promise 对象。这个我们在前文讲创建 Promise 实例的三种方法时就讲过了。


var p = Promise.resolve();
p.then(function () {
 // ...
});

7.5 Promise.reject()

Promise.reject(reason) 方法也会返回一个新的 Promise 实例,该实例的状态为 rejected。需要注意的是,Promise.reject() 方法的参数,会原封不动地作为 reject 的理由,变成后续方法的参数。这一点与 Promise.resolve 方法不一致。


const thenable = {
 then(resolve, reject) {
  reject('出错了');
 }
};

Promise.reject(thenable)
.catch(e => {
 console.log(e === thenable)
})
// true

上面代码中,Promise.reject 方法的参数是一个 thenable 对象,执行以后,后面 catch 方法的参数不是 reject 抛出的“出错了”这个字符串,而是 thenable 对象。

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

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章