ES6 - promise

2 阅读10分钟

概念

简单的说Promise就是一个容器,里面保存着某个未来才会结束的事件的结果。通常是异步操作的结果。


特点

  • 对象的状态不受外界影响。 Promise对象代表一个异步操作,有三种状态:
    1. pending:进行中
    2. fulfilled:已成功
    3. rejected:已失败 只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。

缺点

  • 无法取消Promise,一旦新建它就会立即执行,无法在中途取消
  • 如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
  • 当处于pending状态时,无法得知目前进展到哪一阶段

基本用法

Promise对象是一个构造函数,用来生成Promise实例。

let isSuccess = true

const promise = new Promise(function(resolve,reject){

	if(isSuccess){

		resolve('成功')

	}else{

		reject('失败')

	}

})

promise创建成功后,可以通过.then来获取结果

const promise = new Promise(function(resolve,reject){

	if(isSuccess){

		resolve('成功')

	}else{

		reject('失败')

	}

})

promise.then(value => {

	console.log(value)

})

示例:

function loadImageAsync(url) {

      return new Promise(function (resolve, reject) {

        const image = new Image();

  

        image.onload = function () {

          resolve(image);

        };

  

        image.onerror = function () {

          reject(new Error('Could not load image at ' + url));

        };

  

        image.src = url;

      });

    }

  

    // 正确用法

    loadImageAsync('./bg.png').then((image) => {

      document.querySelector('div').appendChild(image);

    }).catch((error) => {

      console.error('图片加载失败:', error);

    });

图片验证

// 验证图片链接是否有效
async function validateImage(url) {
    try {
        await loadImageAsync(url);
        return true; // 图片有效
    } catch {
        return false; // 图片无效
    }
}

Promise.prototype.then()

then方法时定义在原型对象上的,作用时Promise实例添加状态改变时的回调函数。

  1. then有两个参数,分别时成功和失败的回调:
<script>

	let isLike = false
	
	const promise = new Promise((resolve,reject)=>{
	
	  if(isLike){
	
		resolve('成功')
	
	  }else{
	
		reject('失败')
	
	  }
	
	})
	
	
	
	promise.then(success=>{
	
	  console.log(success)
	
	},error=>{
	
	  console.log(error)
	
	})

</script>
  1. then方法的链式调用 可以指定一组按照次序调用的回调函数。 前一个回调函数,有可能返回的还是一个promise对象,这时后一个回调函数,就会等待该promise对象的状态发生变化,才会被调用。简单的说就是后一个then方法的执行时机就是上一个回调函数的结果发生变化的时候。 关于then方法的链式用法,我们来看几个例子:
    • 用户注册流程
registerUser(userData)
    .then(user => login(user.email, user.password))
    .then(token => getUserProfile(token))
    .then(profile => updateUserPreferences(profile.id, preferences))
    .then(result => {
        console.log('注册流程完成');
    })
    .catch(error => {
        console.error('注册失败:', error);
    });

值得注意的是,如果这个流程中有一个promise返回了reject,那么就会直接去执行catch方法,来捕获错误,终止整个函数的执行


Promise.prototype.catch()

这个方法和then方法的第二个参数的行为是相似的,用来获取到reject返回的结果,也就是发生错误的回调函数

const promise = new Promise(function(resolve, reject) {
  throw new Error('test');
});
promise.catch(function(error) {
  console.log(error);
});

如果promise的状态已经凝固,那么catch就一定不会被触发 ==值得注意的是,我们尽量不要使用then方法的第二个参数来捕获promise的错误,尽量来使用catch方法实现==

==如果没有使用catch方法指定错误处理的回调函数,promise对象抛出的错误不会传递到外层代码,也就是说不会有任何的反应==

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  console.log('everything is great');
});

setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123

someAsyncThing()函数产生的 Promise 对象,==内部有语法错误==。浏览器运行到这一行,会打印出错误提示==ReferenceError: x is not defined==,但是==不会退出进程、终止脚本执行==,2 秒之后还是会输出123。这就是说,==Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”==。

虽然说catch是用来捕获promise内部的错误的,但是它也可以在catch中抛出错误,也就是说,可以链式的捕获前一个catch方法中的错误

someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // 下面一行会报错,因为y没有声明
  y + 2;
}).catch(function(error) {
  console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]

Promise.prototype.finally()

finally方法用于指定不论Promise对象最后状态如何,都会执行的操作

promise
.finally(() => {
  // 语句
});

// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

Promise.all()

这个方法用于将多个Promise实例,包装成一个新的Promise实例。

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

all方法接受一个数组作为参数,数组中都是Promise的实例,如果不是,那么就先调用==resolve==方法,将参数转为Promise实例

p的状态取决于p1,p2,p3,这三个实例的状态:

  • 如果三个都返回成功,那么就将这三个的返回值组成一个数组,传递给p的回调函数
  • 但是只要其中有一个实例返回了reject,那么就会将这个实例返回的reject的值传递给p的回调函数 举个例子:
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON('/post/' + id + ".json");
});

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

注意:

如果作为参数的Promise实例,定义了自己的catch方法,那么一旦被rejected,并不会触发all方法的catch方法


Promise.race()

这个方法也是将多个Promise实例,包装成一个新的Promise实例。

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

只要p1,p2,p3中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。


Promise.resolve()

将现有的对象转为Promise对象 ==这个方法的参数分为四种情况:==

  1. 参数是一个==Promise实例== 如果参数是一个Promise实例,那么Promise.resolve将不做任何修改,==原封不懂地返回这个实例==
  2. 参数是一个==thenable对象== thenable对象指的是具有then的对象,例如:
let thenable = {
	then:function(resolve,reject){
		resolve(43)
	}
}

resolve方法会将这个对象转为Promise对象,然后立即执行thenable对象的then()方法

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function (value) {
  console.log(value);  // 42
});
  1. 参数不是具有then()方法的对象,或者根本就不是对象 如果参数是一个原始值,或者是一个不具有then()方法的对象,那么resolve方法返回一个新的Promise对象,状态为resolved。
const p = Promise.resolve('hello');

p.then(s=>{

  console.log(s)

})
  1. 不带任何参数 直接返回一个resolved状态的Promise对象。
setTimeout(function () {

  console.log("three");

}, 0);

  

Promise.resolve().then(function () {

  console.log("two");

});

  

console.log("one");

![[Pasted image 20251117195131.png]] ==需要注意的是:立即执行resolve()的Promise对象,是在本轮“事件循环”的结束时执行,而不是在下一轮“事件循环”的开始时==


Promise.allSettled()

Promise.allSettled()方法,用来确定一组异步操作是否都结束了(不管成功或失败)。

allSettled方法接收一个数组作为参数,数组中的每一个成员都是一个Promise对象,并返回一个新的Promise对象。只有等到所有的Promise对象有状态变更,返回的Promise对象才会发生状态变更。 ==该方法返回的新的 Promise 实例,一旦发生状态变更,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,它的回调函数会接收到一个数组作为参数,该数组的每个成员对应前面数组的每个 Promise 对象。==

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]

固定的返回格式

// 异步操作成功时
{status: 'fulfilled', value: value}

// 异步操作失败时
{status: 'rejected', reason: reason}

Promise.any()

该方法接收一组Promise实例作为参数,包装成一个新的Promise实例返回 ==只要有一个实例返回了成功,那么就将这个成功的状态传递给any方法的实例返回,只有全部实例返回rejected,才会返回rejected==


Promise.reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  console.log(s)
});
// 出错了

上面代码生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行。

Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

Promise.reject('出错了')
.catch(e => {
  console.log(e === '出错了')
})
// true

上面代码中,Promise.reject()方法的参数是一个字符串,后面catch()方法的参数e就是这个字符串。


Promise.try()

实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。

Promise.resolve().then(f)

上面的写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。

const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now

上面代码中,函数f是同步的,但是用 Promise 包装了以后,就变成异步执行了。

那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?回答是可以的,并且还有两种写法。第一种写法是用async函数来写。

const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next

上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;如果f是异步的,就可以用then指定下一步,就像下面的写法。

(async () => f())()
.then(...)

需要注意的是,async () => f()会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法。

(async () => f())()
.then(...)
.catch(...)

第二种写法是使用new Promise()

const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');
// now
// next

上面代码也是使用立即执行的匿名函数,执行new Promise()。这种情况下,同步函数也是同步执行的。

鉴于这是一个很常见的需求,所以 ES2025 提供了Promise.try()方法替代上面的写法。

const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next

事实上,Promise.try存在已久,Promise 库BluebirdQwhen,早就提供了这个方法。

由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有许多好处,其中一点就是可以更好地管理异常。

function getUsername(userId) {
  return database.users.get({id: userId})
  .then(function(user) {
    return user.name;
  });
}

上面代码中,database.users.get()返回一个 Promise 对象,如果抛出异步错误,可以用catch方法捕获,就像下面这样写。

database.users.get({id: userId})
.then(...)
.catch(...)

但是database.users.get()可能还会抛出同步错误(比如数据库连接错误,具体要看实现方法),这时你就不得不用try...catch去捕获。

try {
  database.users.get({id: userId})
  .then(...)
  .catch(...)
} catch (e) {
  // ...
}

上面这样的写法就很笨拙了,这时就可以统一用promise.catch()捕获所有同步和异步的错误。

Promise.try(() => database.users.get({id: userId}))
  .then(...)
  .catch(...)

事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。