Promise
提供了一种全新的异步编程解决方案,通过链式调用的方式,解决了我们在传统异步编程中回调函数嵌套过深的问题
出现的原因
promise 的出现是为了解决回调地狱的问题的
概述
简单来说 Promise 就是一个容器,里面保存着某个未来才会结束的事件(通常是异步操作)的结果。
从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
两个特点
-
对象的状态不受外界影响
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 -
一旦状态改变,就不会再变,任何时候都可以得到这个结果
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函 数,也会立即得到这个结果。这与事件(Event)完全不同。事件的特点是:如果你错过了它,再去监听,是得不到结果的。
三个缺点
- 无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。 - 如果不设置回调函数,
Promise
内部抛出的错误,不会反应到外部。 - 当处于
pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise 如何解决回调地狱
首先,回调地狱的两个主要问题是:
- 多层嵌套的问题;
- 每种任务的处理结果存在两种可能性(成功或失败),那么需要在每种任务执行结束后分别处理这两种可能性。
Promise 利用了三大技术手段来解决回调地狱:回调函数延迟绑定、返回值穿透、错误冒泡。
回调函数延迟绑定 就是通过 then 方法传入回调函数
返回值穿透 根据 then 回调函数的传入值创建不同类型的 promise, 然后把返回的 Promise 穿透到外层
错误冒泡 then 前面产生的错误会一直向后传递,被 catch 接收到进行处理
用法
基本用法
promise.then:回调函数延迟绑定
返回resolve
const promise = new Promise((resolve, reject) => {
resolve(100)
})
promise.then((value) => {
console.log('resolved', value) // resolve 100
},(error) => {
console.log('rejected', error)
})
返回reject
const promise = new Promise((resolve, reject) => {
reject(new Error('promise rejected'))
})
promise.then((value) => {
console.log('resolved', value)
},(error) => {
console.log('rejected', error)
// rejected Error: promise rejected
// at E:\professer\lagou\Promise\promise-example.js:4:10
// at new Promise (<anonymous>)
})
链式调用
常见误区*:*** 嵌套使用的方式是使用Promise最常见的误区。要使用promise的链式调用的方法尽可能保证异步任务的扁平化。
链式调用的理解:
- promise对象then方法,返回了全新的promise对象。可以再继续调用then方法,如果return的不是promise对象,而是一个值,那么这个值会作为resolve的值传递,如果没有值,默认是undefined
- 后面的then方法就是在为上一个then返回的Promise注册回调
- 前面then方法中回调函数的返回值会作为后面then方法回调的参数
- 如果回调中返回的是Promise,那后面then方法的回调会等待它的结束
异常处理
then中回调的onRejected方法 .catch()(推荐)
promise中如果有异常,都会调用reject方法,还可以使用.catch(); 使用.catch方法更为常见,因为更加符合链式调用
.catch形式和前面then里面的第二个参数的形式,两者异常捕获的区别:
.catch()是对上一个.then()返回的promise进行处理,不过第一个promise的报错也顺延到了catch中,而then的第二个参数形式,只能捕获第一个promise的报错,如果当前then的resolve函数处理中有报错是捕获不到的。
所以.catch是给整个promise链条注册的一个失败回调。推荐使用
ajax('/api/user.json')
.then(function onFulfilled(res) {
console.log('onFulfilled', res)
}).catch(function onRejected(error) {
console.log('onRejected', error)
})
// 相当于
ajax('/api/user.json')
.then(function onFulfilled(res) {
console.log('onFulfilled', res)
})
.then(undefined, function onRejected(error) {
console.log('onRejected', error)
})
全局对象上的unhandledrejection事件
在全局对象上注册一个unhandledrejection事件,处理那些代码中没有被手动捕获的promise异常,当然并不推荐使用
更合理的是:在代码中明确捕获每一个可能的异常,而不是丢给全局处理
// 浏览器
window.addEventListener('unhandledrejection', event => {
const { reason, promise } = event
console.log(reason, promise)
//reason => Promise 失败原因,一般是一个错误对象
//promise => 出现异常的Promise对象
event.preventDefault()
}, false)
// node
process.on('unhandledRejection', (reason, promise) => {
console.log(reason, promise)
//reason => Promise 失败原因,一般是一个错误对象
//promise => 出现异常的Promise对象
})
静态方法
Promise.resolve()
快速的将一个值转换为 Promise 对象
- 接收一个常量将其转换为 Promise 对象
- 接收一个 Promise 对象将返回自身
- 接收一个带有 then 方法的对象(then 方法类似于 Promise的 then 方法),这个对象也可以作为一个 Promise 被执行
Promise.reject()
快速创建一个一定是失败的 Promise 对象
Promise.all()
语法: Promise.all(iterable)
参数: 一个可迭代对象,如 Array。
描述: 此方法对于汇总多个 promise 的结果很有用,在 ES6 中可以将多个 Promise.all 异步请求并行操作,返回结果一般有下面两种情况。
当所有结果成功返回时按照请求顺序返回成功。
当其中有一个失败方法时,则进入失败方法。
Promise.allSettled()
Promise.allSettled 的语法及参数跟 Promise.all 类似,其参数接受一个 Promise 的数组,返回一个新的 Promise。唯一的不同在于,执行完之后不会失败,也就是说当 Promise.allSettled 全部处理完成后,我们可以拿到每个 Promise 的状态,而不管其是否处理成功。
const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// 返回结果:
// [
// { status: 'fulfilled', value: 2 },
// { status: 'rejected', reason: -1 }
// ]
Promise.any()
语法: Promise.any(iterable)
参数: iterable 可迭代的对象,例如 Array。
描述: any 方法返回一个 Promise,只要参数 Promise 实例有一个变成 fulfilled 状态,最后 any 返回的实例就会变成 fulfilled 状态;如果所有参数 Promise 实例都变成 rejected 状态,包装实例就会变成 rejected 状态。
const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const anyPromise = Promise.any([resolved, rejected]);
anyPromise.then(function (results) {
console.log(results);
});
// 返回结果:
// 2
Promise.race()
语法: Promise.race(iterable)
参数: iterable 可迭代的对象,例如 Array。
描述: race 方法返回一个 Promise,只要参数的 Promise 之中有一个实例率先改变状态,则 race 方法的返回状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 race 方法的回调函数。
我们来看一下这个业务场景,对于图片的加载,特别适合用 race 方法来解决,将图片请求和超时判断放到一起,用 race 来实现图片的超时判断。
//请求某个图片资源
function requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){ resolve(img); }
img.src = 'http://www.baidu.com/img/flexible/logo/pc/result.png';
});
return p;
}
//延时函数,用于给请求计时
function timeout(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){ reject('图片请求超时'); }, 5000);
});
return p;
}
Promise.race([requestImg(), timeout()])
.then(function(results){
console.log(results);
})
.catch(function(reason){
console.log(reason);
});
从上面的代码中可以看出,采用 Promise 的方式来判断图片是否加载成功,也是针对 Promise.race 方法的一个比较好的业务场景 。
执行时序:宏任务/微任务
回调队列中的任务称为 宏任务,宏任务执行过程中可以临时加上一些额外需求,这些额外的需求可以选择作为一个新的宏任务进到队列中排队,也可以作为当前任务的 微任务(直接在当前任务执行过后立即执行),Promise 的回调会作为微任务进行执行
微任务就是为了提高整体的响应能力
举个栗子
有一个大爷去银行存款,排队等到他去存款的时候突然想要开通手机银行,这时候银行柜员会优先完成大爷的需求再开始其他人的业务。 大爷去存款:宏任务 大爷要开通手机银行: 微任务
常见的宏任务: setTimeout, setInterval..
常见的微任务: promise.then, process.nextTick、MutationObserver
手写 Promise
Promise/A+ 规范
官方地址:Promises/A+
以下为摘录的一些关键部分
Terminology 【术语】
- “promise” is an object or function with a
then
method whose behavior conforms to this specification. 【promise是一个具有then 方法的对象或者方法,他的行为符合该规范】 - “thenable” is an object or function that defines a
then
method.【thenable是一个定义了then方法的对象或者方法】 - “value” is any legal JavaScript value (including
undefined
, a thenable, or a promise).【value是任何一个合法的JS值(包括 undefined、thenable 或 promise)】 - “exception” is a value that is thrown using the
throw
statement.【exception是一个被throw语句抛出的值-异常】 - “reason” is a value that indicates why a promise was rejected.【reason是promise 里reject之后返回的原因】
Promise States 【Promise状态】
A promise must be in one of three states: pending, fulfilled, or rejected.【一个 Promise 有三种状态:pending、fulfilled 和 rejected】
-
When pending, a promise:
-
- may transition to either the fulfilled or rejected state.
- 【当状态为 pending 状态时,即可以转换为 fulfilled 或者 rejected 其中之一】
-
When fulfilled, a promise:
-
- must not transition to any other state.
- must have a value, which must not change.
- 【当状态为 fulfilled 状态时,就不能转换为其他状态了,必须返回一个不能再改变的值】
-
When rejected, a promise:
-
- must not transition to any other state.
- must have a reason, which must not change.
- 【当状态为 rejected 状态时,同样也不能转换为其他状态,必须有一个原因的值也不能改变】
Here, “must not change” means immutable identity (i.e. ===
), but does not imply deep immutability.
The then
Method【then 方法】
A promise must provide a then
method to access its current or eventual value or reason.【一个 Promise 必须拥有一个 then 方法来访问它的值或者拒绝原因。】
A promise’s then
method accepts two arguments:【then 方法有两个参数】
promise.then(onFulfilled, onRejected)
-
Both
onFulfilled
andonRejected
are optional arguments: -
- If
onFulfilled
is not a function, it must be ignored. - If
onRejected
is not a function, it must be ignored. - 【onFulfilled 和 onRejected 都是可选参数。】
- If
-
If
onFulfilled
is a function: -
- it must be called after
promise
is fulfilled, withpromise
’s value as its first argument. - it must not be called before
promise
is fulfilled. - it must not be called more than once.
- 【如果 onFulfilled 是函数,则当 Promise 执行结束之后必须被调用,最终返回值为 value,其调用次数不可超过一次】
- it must be called after
-
If
onRejected
is a function, -
- it must be called after
promise
is rejected, withpromise
’s reason as its first argument. - it must not be called before
promise
is rejected. - it must not be called more than once.
- 【如果 onFulfilled 是函数,则当 Promise 执行结束之后必须被调用,最终返回reason,其调用次数不可超过一次】
- it must be called after
-
then
may be called multiple times on the same promise. -
- If/when
promise
is fulfilled, all respectiveonFulfilled
callbacks must execute in the order of their originating calls tothen
. - If/when
promise
is rejected, all respectiveonRejected
callbacks must execute in the order of their originating calls tothen
. - 【then方法可以被一个Promise多次调用】
- If/when
-
then
must return a promise [3.3]. promise2 = promise1.then(onFulfilled, onRejected); -
- If either
onFulfilled
oronRejected
returns a valuex
, run the Promise Resolution Procedure[[Resolve]](promise2, x)
. - If either
onFulfilled
oronRejected
throws an exceptione
,promise2
must be rejected withe
as the reason. - If
onFulfilled
is not a function andpromise1
is fulfilled,promise2
must be fulfilled with the same value aspromise1
. - If
onRejected
is not a function andpromise1
is rejected,promise2
must be rejected with the same reason aspromise1
. - 【then方法必须返回一个promise】
- If either
Promise实现
首先分析其原理
- promise 就是一个类: 在执行类的时候需要传递一个执行器,执行器会立即执行
- promise 的三种状态: pending, fulfilled, or rejected
- resolve 和 reject 方法用来更改状态 resolve -> fulfilled / reject -> rejected
- then 方法判断状态: 成功状态 -> 调用成功回调, 失败状态 -> 调用失败回调
1. 完成一个最简单的Promise
// 三种状态
const PENDING = 'pending', FULFILLED = 'fulfilled', REJECTED = 'rejected'
class MyPromise {
constructor(excutor) {
excutor(this.resolve, this.reject);
}
status = PENDING; // 当前的状态
value = undefined; // 成功的值
reason = undefined; // 失败的值
resolve = (value) => {
// must not transition to any other state
if (this.status !== PENDING) return
this.status = FULFILLED;
this.value = value
}
reject = (reason) => {
// must not transition to any other state
if (this.status !== PENDING) return
this.status = REJECTED;
this.reason = reason
}
then(onFulfilled, onRejected) {
switch (this.status) {
case FULFILLED:
onFulfilled(this.value);
break;
case REJECTED:
onRejected(this.reason);
break;
}
}
}
test
const Promise = require('./index');
const res_1 = new Promise(function(resolve, reject) {
// resolve('最简单的Promise:resolve');
reject('最简单的Promise:reject')
})
res_1.then(res => {
console.log('success', res)
}, err => {
console.log('rejected', err)
})
2. 异步Promise
第一种情况下没有处理到异步的情况;异步情况的处理其实就是then方法中对应pending状态的处理,如下所示:
class MyPromise {
// 添加相应值
onFulfilled = undefined; // 存储成功后的回调
onRejected = undefined; // 存储失败后的回调
resolve = (value) => {
// must not transition to any other state
if (this.status !== PENDING) return
this.status = FULFILLED;
this.value = value;
// 成功之后立即执行缓存中的回调
this.onFulfilled && this.onFulfilled(this.value)
}
reject = (reason) => {
// must not transition to any other state
if (this.status !== PENDING) return
this.status = REJECTED;
this.reason = reason
this.onRejected && this.onRejected(this.reason)
}
then(onFulfilled, onRejected) {
switch (this.status) {
case FULFILLED:
onFulfilled(this.value);
break;
case REJECTED:
onRejected(this.reason);
break;
default: // pending状态
this.onFulfilled = onFulfilled
this.onRejected = onRejected
}
}
}