Promise 对象详解

192 阅读10分钟

💡 前端异步编程经历了 callback、promise、generator、async/await 几个阶段。

💡 目前在简单场景使用 callback,复杂场景使用 promise 和 async/await,由于generate 的api不易理解并且不易于使用,所以很少使用

Promise 的含义

Promise 是异步编程的一种解决方案,比传统的回调函数和事件更合理更强大,解决了回调地狱的问题

从语法上说,Promise 是一个对象,从它可以获取异步操作的信息。

Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

状态特性 让使用 promise 的用户可以及时通知 promise 任务执行结果。

then特性 让使用 promise 的用户可以控制 执行完一个任务后执行下一个任务。

使用回调进行异步编程的话,都是用户手动控制的,使用promise的话,只需要告诉promise:“我要执行什么任务”、“我执行的任务结束了”、“然后我要做什么”。

Promise 对象的两个特点:

  1. **对象的状态不受外界影响**。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Fulfilled(已成功)、Rejected(已失败)。
  2. 一旦状态改变就不会再变,任何时候都可以得到这个结果。状态改变只有两种可能:从pending到Fulfilled 和 从Pending到Rejected.

Promise 的缺点:

  1. **无法取消Promise**。一旦新建就会立即执行,无法中途取消。
  2. 如果不设置回调函数,Promise 内部抛出的错误不会反应到外部
  3. **当处于Pending状态时,无法得知目前进展到哪一个阶段**(刚开始或即将完成)。

基本用法

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

const promise = new Promise(function (resolve, reject) {
	// ...some code
	if (异步操作成功)
		resolve(value)
	else
		reject(error)
});

Promise 函数接受一个函数作为参数,该函数有两个参数:resolve和reject

resolve 函数的作用是,将状态从 Pending变成Resolved,操作成功时调用。reject函数是,将状态变成 rejected,失败时调用。

then 方法可以接受两个回调函数作为参数。第一个是状态变成Resolved时调用,第二个是变成rejected时调用。其中第二个参数是可选的。这两个函数都接受promise对象传出的值作为参数【下方then方法中有具体说明】。

Promise新建后回立即执行。然后,then 方法指定的回调函数将在当前脚本所有同步任务执行完成后才会执行。

// 使用Promise 包装一个图片加载的异步操作
function loadImageAsync(url) {
	return new Promise(function(resolve, reject) {
		var image = new Image();
		image.onload = function() {
			resolve(image)
		}
		image.onerror = function() {
			reject(new Error(`could not load image at ${url}`))
		}
		image.src = url;
	})
}

**// 由于抛出错误,promise状态已经改变为rejected,再调用resolve将不会改变promise状态**
const p = new Promise((resolve, reject) => {
  throw new Error('test-error');
	resolve('test');
});

p.then(
	data => console.log(1, 'resolve', data),
  data => console.log(1, 'reject', data)
);
// 执行结果
1 "reject" Error: test-error

// 增加一道代码,面试题:如果图片加载不出来,用另一个替代
function loadImageAsync(url) {
	var image = new Image();
	return new Promise(function(resolve, reject) {
		image.src = url;
		document.body.appendChild(image)
		image.onload = function() {
			resolve('load img success')
		}
		image.onerror = function() {
			img.src = '替换的路径'
			reject('load img error')
		}
		
	})
}
loadImageAsync('图片地址').then(res => {
	console.log(res)
}).catch(err => {
	console.log(err)
})

promise对象的方法:then、catch、all、race

Promise.prototype.then() 为Promise实例添加状态改变时的回调函数

then方法接受两个参数,onFulfilled(状态变为fullfilled的回调)和onRejected(状态变为rejected的回调)。

返回一个新的promise对象,返回的 promise对象的状态与 then的参数(onFulfilled、onRejected)和onFulfilled、onRejected方法中返回的值有关。

  • 1、then 方法不传参数
    如果不传参数,则 then方法返回的promise 和 调用then的promise的状态 一致。
    更具体地,如果 没有onFullfilled参数 并且promise的状态为fullfilled,那么 then方法返回的promise 和 调用then方法的promise状态 一致
    如果 没有onRejected参数 并且promise状态为rejected,那么then方法返回的promise 和调用then方法的promise状态 一致
    可以简单地理解:如果上一个promise不处理,那就下一个promise处理

    var p = new Promise(resolve => {
        throw new Error('test');
    });
    
    p.then(
        () => {}     // 没有onRejected参数
    ).then(
        data => console.log('resolve', data),
        err => console.log('reject', err)
        
    );
    // 执行结果  reject Error: test
    
    var p = new Promise(resolve => {
        resolve('test');
    });
    
    p.then(
        undefined, () => {}    // 没有onFulfilled参数
    ).then(
        data => console.log('resolve', data),
        err => console.log('reject', err) 
    );
    // 执行结果   resolve test
    
  • 2、回调不返回值

    无论onFullfilled中还是onRejected中,不返回值(即默认返回undefined),则then返回的新promise的状态变为fullfilled,值为undefined

    var p = new Promise(resolve => {
        resolve('test');
    });
    
    p.then(
        () => {}
    ).then(
        data => console.log('resolve', data),
        err => console.log('reject', err)
    );
    // 执行结果   resolve undefined
    
    var p = new Promise(resolve => {
        throw new Error('test');
    });
    
    p.then(
        () => {},
        () => {}
    ).then(
        data => console.log('resolve', data),
        err => console.log('reject', err)
    );
    
    // 执行结果   resolve undefined
    
  • 3、返回普通值
    无论onFullfilled中还是onRejected中,返回普通值,则then返回的新promise的状态变为fullfilled,值为这个值。

    普通值指的是,非promise对象、非thenable对象(含有then方法的对象)

    var p = new Promise(resolve => {
        resolve('test');
    });
    
    p.then(
        () => {return 'a'},
        () => {return {b: 1}}
    ).then(
        data => console.log('resolve', data),
        err => console.log('reject', err) 
    );
    
    // 执行结果   resolve a
    
  • 4、返回Promise对象

    无论onFullfilled中还是onRejected中,返回一个promise对象,则以 该promise的任务和状态返回新的promise

    var p = new Promise(resolve => {
        throw new Error('test');
    });
    
    p.then(
        () => {},
        () => {return Promise.resolve('yes');}
    ).then(
        data => console.log('resolve', data),
        err => console.log('reject', err)
    );
    
    // 执行结果   resolve yes
    
  • 5、返回thenable对象(含有then方法的对象)

    无论onFullfilled中还是onRejected中,返回一个thenable对象,则调用该对象的then方法,

    该then方法接收两个参数resolvePromise和rejectPromise

    如果then中调用了resolvePromise,则返回的promise状态置为fullfilled,

    如果then中调用了rejectPromise,或者then中抛出异常,则返回的Promise状态置为rejected,

    在调用resolvePromise或者rejectPromise之前,返回的promise处于pending状态

    // then中调用了resolvePromise,则返回的promise状态置为fullfilled
    var p = new Promise((r) => {throw new Error('test')});
    p.then(
      () => ({
    		then: function(resolvePromise, rejectPromise) {resolvePromise('resolvePromise')}
    	}),
      e => ({
    		then: function(resolvePromise, rejectPromise) {rejectPromise('rejectPromise')}
    	})
    ).then(
      data => console.log('resolve', data),
      e => console.log('reject', e)
    );
    // 执行结果  reject rejectPromise
    
    // 在调用resolvePromise或者rejectPromise之前,返回的promise处于pending状态
    var p = new Promise((r) => {throw new Error('test')});
    p.then(
      () => ({
    		then: function(resolvePromise, rejectPromise) {}
    	}),
      e => ({
    		then: function(resolvePromise, rejectPromise) {}
    	})
    ).then(
        data => console.log('resolve', data),
        e => console.log('reject', e)
    );
    // 执行结果  promise 处于pending状态
    
    // then中调用了rejectPromise,或者then中抛出异常,则返回的Promise状态置为rejected
    var p = new Promise((r) => {throw new Error('test')});
    p.then(
      () => {return {
    		then: function(resolvePromise, rejectPromise) {resolve('resolvePromise')}
    	}},
      e => {return {
    		then: function(resolvePromise, rejectPromise) {throw new Error('surprise')}
    	}}
    ).then(
        data => console.log('resolve', data),
        e => {console.error('reject', e)}
    );
    // 执行结果  reject Error: surprise
    
  • 6、抛出异常

    无论onFullfilled中还是onRejected中,抛出错误,则以rejected为状态返回新promise

    var p = new Promise(resolve => {resolve('test')});
    p.then(
        () => {throw new Error('1')},
        e => {return true}
    ).then(
        data => console.log('resolve', data),
        e => {console.error('reject', e)}
    );
    
    // 执行结果  reject Error: 1
    

Promise.prototype.catch() 用于指定发生错误时的回调函数

等同于 .then(null, rejection) 或 .then(undefined, 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));

一般来说,不要在 then 方法中定义 Rejected 状态的回调函数,应使用 catch 方法。理由是第二种写法可以捕获前面 then 方法执行中的错误,也更接近同步的写法(try/catch)。

catch 方法返回的还是一个Promise对象,因此后面还可以接着调用 then 方法。catch后面的then方法报错与前一个catch无关。

var p = new Promise(resolve => {
    resolve((x+2));
});
p.then(
	(x) => { x }
).catch(
	error => { 
		console.log('第一个catch:', error); 
		return '第一个catch return出去的值' 
	}
).then(
	(v) => console.log('第二个then:', v)
).then(
	() => y + 2
).catch(
	error => { console.log('第二个catch', error) }
)
/**
打印结果
第一个catch: ReferenceError: x is not defined
第二个then: 第一个catch return出去的值
第二个catch ReferenceError: y is not defined
*/

Promise.all() 用于将多个Promise实例包装成一个新的Promise实例

Promise.all方法用于多个异步任务执行,当所有任务都正常完成时候,再做后面处理的场景。

Promise.all方法接收一个 promise数组 作为参数,返回一个promise,当参数的数组中的所有promise都resolve时候,返回的promise才会resolve;而若有一个参数的数组中的promise reject,返回的promise就会reject。

Promise.all方法返回的promise的then的第一个参数onFullfilled回调的参数也是一个数组,对应参数中的数组promise resolve的结果。

如果作为参数的 Promise 实例自身定义了 catch 方法,那么它被 rejected 时并不会触发 Promise.all() 的catch 方法。

const p1 = new Promise((resolve, reject) => {
	resolve('hello');
}).then(result => result).catch(e => e)

const p2 = new Promise((resolve, reject) => {
	throw new Error('报错了')
}).then(result => result).catch(e => e)

Promise.all([p1, p2])
.then(result => console.log('then:', result))
.catch(e => console.log('catch:', e))

// then: ['hello', Error: 报错了]

上面的代码中,p1会 resolved,p2首先会rejected,但是p2有自己的 catch 方法,该方法返回的是一个新的 Promise 的实例,p2 实际上指向的是这个实例。该实例执行完 catch 方法后会变成 resolved,导致 Promise.all() 方法参数里面的两个实例都会 resolved,因此会调用 then 方法指定的回调函数,而不会调用 catch 方法值定的回调函数。如果 p2 没有自己的 catch 方法,就会调用 Promise.all() 的 catch 方法。

Promise.race() 将多个Promise实例包装成一个新的promise实例

Promise.race 方法用于多个异步任务执行,当有其中一个任务完成或失败时候,就执行后续处理的场景。

Promise.race接收一个promise数组作为参数,返回一个新的promise。当参数数组中其中一个promise resolve或者reject,返回的promise就相应地改变状态。

var p1 = Promise.reject(1);
var p2 = new Promise(resolve => {
	setTimeout(() => {
  	resolve(2);
  }, 1000);
});

Promise.race([p1, p2])
.then(
		data => {console.log('resolve', data);},
    e => {console.log('reject', e);}
);
// 执行结果 reject 1

// 如果在五秒内没有获取到结果,就返回rejected
const p = Promise.race([
	fetch('api/list'),
	new Promise(function(resolve, reject) {
		setTimeout(() => reject(new Error('request timeout')), 5000)
	})
])
p.then(response => console.log(response)).catch(error => console.log(error))

Promise.allSettled() 用于多个异步任务都结束(完成或者失败)时候,再执行后续任务的场景

Promise.allSettled用于多个异步任务都结束(完成或者失败)时候,再执行后续任务的场景

Promise.allSettled接收一个promise数组作为参数,返回一个promise。当参数数组中所有promise状态改变后,返回的promise变为fullfilled状态。

返回的promise的onFullfilled参数接收一个结果数组作为参数,数组对应Promise.allSettled传入的promise数组。

结果数组每个元素是一个对象,格式固定:{status, value, reason},标识状态、resolve返回值、reject原因

var p1 = Promise.reject(1);
var p2 = new Promise(resolve => {
	setTimeout(() => {
  	resolve(2);
  }, 1000);
});

Promise.allSettled([p1, p2])
.then(
	data => {console.log('resolve', data);},
);

// 执行结果  
// resolve [{status: "rejected", reason: 1}, {status: "fulfilled", value: 2}]

*

Promise.resolve() 可以将现有对象转成 Promise 对象

Promise.resolve(’foo’) 等价于 new Promise(resolve ⇒ resolve(’foo’))

Promise.resolve方法的参数分为 4 种情况:

  1. **参数是一个Promise实例** 如果是promise实例,那么将不做任何修改,原封不动的返回这个实例

  2. 参数是一个 thenable 对象 - 带有 then 方法的对象 Promise.resolve方法会将这个对象转成Promise对象,然后立即执行thenable对象的then方法。

  3. 参数不是具有 then 方法的对象 或 根本不是对象 Promise.resolve方法将会返回一个新的Promise对象,状态为Resolved。

  4. 不带有任何参数 Promise.resolve方法允许调用时不带有参数,直接返回一个Resolved状态的Promise对象。所以,如果希望得到一个Promise对象,比较方便的方法就是直接调用 Promise.resolve 方法。

    setTimeout(function() { console.log(1) }, 0) // 不带有任何参数 Promise.resolve().then(function() { console.log(2) }) // 参数是一个 thenable 对象 let thenable = { then: function(resolve, reject) { resolve(3) } } let p1 = Promise.resolve(thenable); p1.then(function(v) { console.log(v) }) // 参数不是具有 then 方法的对象 或 根本不是对象 let p2 = Promise.resolve(4); p2.then(function(v) { console.log(v) })

    console.log(5)

    // 执行结果:5 2 4 3 1

Promise.reject() 返回一个新的Promise实例,状态为Rejected

Promise.reject方法的参数会原封不动的作为 reject 的理由变成后续方法的参数。这一点与Promise.resolve方法不一致。

var p = Promise.reject('出错了') 
// 等同于 
var p = new Promise((resolve, reject) ⇒ reject('出错了'))
p.then(null, function(err) ⇒ {
	console.log(err)
})