先进行一些基本的概念梳理:
什么是 Promise?
Promise
是异步编程的一种解决方案:从语法上讲,Promise
是一个对象,它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。
Promise 可以解决什么问题?
- 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象
当异步场景越来越多的时候,代码会横向发展(变宽),依赖的层级逐渐变多,会不停的进行嵌套 ,最终导致回调地狱。Promise
实际上是使异步调用更加链式了,可以将异步操作队列化,按照期望的顺序依次进行执行,返回符合预期的结果。
Promise
可以支持多个并发的请求,获取并发请求中的数据Promise
可以解决异步的问题,但本身不能说 promise 是异步的
举个 🌰
//回调函数
const callback=function(){
console.log('callback')
}
function callbackFn(fn){
setTimeout(fn,3000)
}
callbackFn(callback)
//封装成promise
const promise =new Promise(function(resolve,reject){
console.log('success')
resolve()
})
promise.then(
function(){
setTimeout(callback,3000)
}
)
Promise 优缺点
优点
- 把执行代码和处理代码分离开,使异步操作逻辑更加清晰。
- 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数(并未剥夺函数
return
的能力,因此无需层层传递callback
,进行回调获取数据) - 对象提供统一的接口,使得控制异步操作更加容易
- 代码风格,容易理解,便于维护
缺点
- 无法取消
Promise
,一旦新建它就会立即执行,无法中途取消 - 如果不设置回调函数,
Promise
内部抛出的错误,不会反应到外部 - 当处于
Pending
状态时,无法得知目前进展到哪一个阶段
Promise 三种状态
- 初始状态:
pending
(等待态) - 操作成功:
fulfilled
(成功态) - 操作失败:
rejected
(失败态)
Promise 使用
Promise
的构造函数接收一个函数作为参数,并且这个函数需要传入两个参数:
resolve()
: 异步操作执行成功后的回调函数,将Promise
对象的状态从“未完成”变为“成功”(即从pending
变为resolved
),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject()
: 异步操作执行失败后的回调函数,将Promise
对象的状态从“未完成”变为“失败”(即从pending
变为rejected
),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。在执行resolve
的回调时,如果抛出异常了(代码出错了),那么并不会报错卡死js
,而是会进到这个catch
方法中。- 当
Promise
状态发生改变,就会触发then()
里的响应函数处理后续步骤;创造Promise
实例后,它会立即执行,Promise
状态一经改变,不会再变。 - 当出现
.then()
里面有.then()
的情况时,由于.then()
返回的还是 Promise 实例,会等里面的.then()
执行完,再执行外面的
let p = new Promise((resolve, reject) => {
//做一些异步操作
setTimeout(function(){
var num = Math.ceil(Math.random()*10); //生成1-10的随机数
if(num<=5){
resolve(num);
}
else{
reject('数字太大了');
}
}, 2000);
});
p.then((data) => {
console.log('resolved',data);
},(err) => {
console.log('rejected',err);
}
);
Promises/A+ 规范(划重点)*
其实归根结底,Promises/A+
规范为整个Promise
的执行和使用提供了完整的解释,所有对Promise
的疑问都能在这里得到解答。
先上一个官方地址:https://promisesaplus.com
由于官网是英文版本,为了便于阅读和理解,结合 Google 翻译自己转译了一下,水平有限,有问题欢迎指正,感恩的心 💗💗
规范正文:(举 🌰 补充)
**一个由实施者提供的开放的,可实现的,可互操作的 JavaScript 承诺的标准,对于实施者。**
一个`Promise`表示异步操作的最终结果。与承诺进行交互的主要方式是通过其`then`方法,该方法注册回调以接收承诺的最终值或无法实现承诺的原因。
该规范详细介绍了该`then`方法的行为,提供了可互操作的基础,所有`Promises / A +`兼容的`Promise`实现都可以依靠该基础来提供。因此,该规范应被认为是非常稳定的。尽管 Promises / A +组织有时会通过向后兼容的微小更改来修订此规范,以解决新发现的极端情况,但只有经过仔细考虑,讨论和测试之后,我们才会集成大型或向后兼容的更改。
从历史上看,`Promises / A +`阐明了较早的`Promises / A`提案的行为条款,将其扩展为涵盖事实上的行为,并省略了未指定或有问题的部分。
最后,核心的`Promises / A +`规范不涉及如何创建,履行或拒绝诺言,而是专注于提供一种可互操作的`then`方法。未来工作中伴随着这些规范可能涉及到这些主题。
术语
Promise
是具有 then 方法的符合本规范的对象或函数。(狭义认为一个对象只要有 then 方法就是一个合法的 promise 对象)thenable
(包含then
方法)是定义then
方法的对象或函数。value(值)
是任何合法的JavaScript
值(包括undefined
,thenable
或promise
)。exception(异常)
是使用该 throw 语句引发的值。reason(原因)
是表明拒绝承诺的原因的值。
要求
promise 的状态
一个promise
必须处于以下三种状态之一:等待中(pending
),已完成(fulfilled
)或已拒绝(rejected
)。
- 处于等待中:
- 可能会转换为已完成或已拒绝状态。
- 当已完成时:
- 不得过渡到任何其他状态。
- 必须具有一个值,该值不能更改。
- 当已拒绝时:
- 不得过渡到任何其他状态。
- 必须有一个原因,不能改变。
在此,“不得更改”是指不变的身份(即===),但并不表示深层的不变性。
一个 then 方法
一个承诺必须提供一个then
方法来访问其当前或最终值或原因。
Promise
的then
方法接受两个参数:
promise.then(onFulfilled, onRejected)
这两个
onFulfilled
和onRejected
是可选的参数:- 如果
onFulfilled
不是函数,则必须将其忽略。 - 如果
onRejected
不是函数,则必须将其忽略。
- 如果
如果
onFulfilled
是一个函数:- 必须在
promise 执行结束后调用,以
promise` 的结果作为第一个参数。 - 在
promise
执行结束之前不能调用它。 - 不能多次调用。
- 必须在
如果
onRejected
是一个函数:- 必须在
promise
被拒绝之后调用,以promise
的原因作为第一个参数。 - 在
promise
被拒绝之前不能调用它。 - 不能多次调用。
- 必须在
onFulfilled
或onRejected
在执行上下文堆栈仅包含平台代码之前不得调用。onFulfilled
和onRejected
必须作为普通函数调用(即没有 this 值)(非实例化调用,this
在非严格模式下会指向window
)。then 可能在同一 promise 中多次被调用。
- 当
promise
完成时,则所有相应的onFulfilled
必须按照其注册顺序执行then
。 - 当
promise
被拒绝时,则所有相应的onRejected
必须按照其注册顺序执行then
。promise2 = promise1.then(onFulfilled, onRejected);
- 当
then
必须返回promise
。- 如果有一个
onFulfilled
或onRejected
返回一个值 x,promise2
进入onFulfilled
状态。[1] - 如果任何一个
onFulfilled
或onRejected
引发一个异常 e,则promise2
必须拒绝 e 并返回拒绝原因。[2] - 如果
onFulfilled
不是函数且promise1
已完成,则promise2
必须使用相同的值来完成promise1
(成功)。[3] - 如果
onRejected
不是函数而promise1
被拒绝,则promise2
必须以与相同的理由将其拒绝(失败)。[4]
- 如果有一个
(每一个 promise
只取决于上一个 promise
的结果)
对照上面四条标准,这个地方举几个 🌰 来看一下 promise 的输出:(判断一下以下几种情况分别输出什么?)
(1)
const promise1 = new Promise(function (resolve, reject) {
reject()
})
promise1
.then(function () {
return 123;
}, null)
.then(null, null)
.then(null, null)
.then(function () {
console.log('success promise')
}, function () {
console.log('error promise')
})
输出error promise
首先promise1
进入onRejected
状态,返回的值为null
,不是一个函数,参照上方第[4]条,不停向下onRejected
直到console.log('error promise')
(2)
const promise1 = new Promise(function (resolve, reject) {
reject()
})
promise1
.then(null,function () {
return 456;
})
.then(null, null)
.then(null, null)
.then(function () {
console.log('success promise')
}, function () {
console.log('error promise')
})
输出success promise
首先promise1
进入onRejected
状态,函数返回一个值为 456,参照上方第[1]条,此时promise2
进入 onFulfilled
状态,参照上方第[4]条,不停向下完成直到console.log('success promise')
(3)
const promise1 = new Promise(function (resolve, reject) {
resolve()
})
promise1
.then(null, function () {
return 456;
})
.then(null, null)
.then(null, null)
.then(function () {
throw Error('e')
}, null)
.then(function () {
console.log('success promise')
}, function () {
console.log('error promise')
})
输出error promise
首先promise1
进入onFulfilled
状态,返回null
,参照上方第[4]条,不停向下完成直到抛出异常e
,参照上方第[2]条,此时进入onRejected
状态,向下输出console.log('success promise')
promise 解决过程
promise
解决过程是一个抽象的操作,输入一个 promise
和一个值,它表示为[[Resolve]](promise, x)
。如果 x 是 thenable
的,则其行为类似于 promise
,尝试使 promise
采用 x 的状态。否则,它将使用 value
。
只要对约定的实现公开 Promises / A +
兼容的 then
方法,对约定的实现就可以进行互操作。它还允许Promises / A +
实现以合理的 then
方法“同化”不合格的实现。
要运行[[Resolve]](promise, x)
,请执行以下步骤:
- 如果
promise
和x
引用相同的对象,则以TypeError
为理由拒绝promise
。 - 如果
x
是一个promise
,则采用其状态:- 如果
x
等待中,则promise
必须保持等待状态,直到x
已完成或已拒绝。[1] - 如果
x
已完成,promise
则以相同的值完成。[2] - 如果
x
已拒绝,promise
则以相同的理由拒绝。[3](.then()的状态取决于返回的 promise 的状态)
- 如果
再举个 🌰
var promise1 =new Promise(function(resolve,reject){
resolve('promise1')
})
var promise2 =new Promise(function(resolve,reject){
resolve('promise2')
})
promise1.then(function(val){
return promise2
}).then(function(val){
console.log(val)
})
输出promise2
,参照上方第[2]条
- 否则,如果
x
是对象或函数(不太常见)- 先执行
x.then
。 - 如果取
x.then
中值时抛出异常的结果 e,以 e 作为原因拒绝promise
。 - 如果
then
是函数,请使用x
作为this
,第一个参数resolvePromise
和第二个参数rejectPromise
进行调用,其中:- 如果
resolvePromise
调用y
作为值,请运行[[Resolve]](promise, y)
。 - 如果
rejectPromise
调用r
作为拒因,以r
作为原因拒绝promise
。 - 如果同时调用
resolvePromise
和rejectPromise
,或者对同一个参数进行了多次调用,则第一个调用优先,其他任何调用都将被忽略。 - 如果调用
then
引发异常e
,- 如果
resolvePromise
或rejectPromise
已经被调用,则忽略它。 - 否则,以
e
作为原因拒绝promise
。
- 如果
- 如果
- 如果
then
不是函数,promise
调用x
变为已完成状态。
- 先执行
- 如果
x
不是一个对象或函数,promise
调用x
变为已完成状态(常见)。
又来一个 🌰
var promise1 =new Promise(function(resolve,reject){
resolve('promise1')
})
var promise2 =new Promise(function(resolve,reject){
resolve('promise2')
})
promise1.then(function(val){
return val
}).then(function(val){
console.log(val)
})
输出promise1
,x
不是一个对象或函数
如果使用在一个循环链中的 thenable
来解决 promise
,[[Resolve]](promise, thenable)
会导致递归,[[Resolve]](promise, thenable)
将被再次调用,以上会导致无限递归。推荐(但不是必需),检测这种递归并以TypeError
为理由拒绝 promise
。
手写一个Promise
根据promiseA+实现一个自己的promise,这里不多讲解了,文末会推荐几篇自己觉得比较好的文章
(其实是我自己在写的还没完善好,等写满意了再回来补坑)
介绍几个Promise常用的方法
Promise.all
谁跑的慢,以谁为准执行回调。all接收一个数组参数,里面的值最终都算返回Promise对象
并行执行异步操作的能力(没有顺序),并且在所有异步操作执行完后才执行回调。
const promise = function (time) {
return new Promise(function (resolve, reject) {
return setTimeout(resolve(time), time);
})
}
Promise.all([promise(1000), promise(3000)]).then(function (values) {
console.log('all', values.toString()) // 三个都成功则成功
},function(e){
console.log(e) // 只要有失败,则失败
})
3秒后输出 all 1000,3000
Promise.race
谁跑的快,以谁为准执行回调。
const promise = function (time) {
return new Promise(function (resolve, reject) {
return setTimeout(resolve(time), time);
})
}
Promise.race([promise(1000),promise(3000)]).then(function(value){
console.log('race',value)
})
1秒后输出 race 1000
可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作
//请求某个图片资源
function requestImg(){
var p = new Promise((resolve, reject) => {
var img = new Image();
img.onload = function(){
resolve(img);
}
img.src = '图片的路径';
});
return p;
}
//延时函数,用于给请求计时
function timeout(){
var p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('图片请求超时');
}, 5000);
});
return p;
}
Promise.race([requestImg(), timeout()]).then((data) =>{
console.log(data);
}).catch((err) => {
console.log(err);
});
巨人的肩膀(包括认为比较好的手写Promise文章)
- Promise不会??看这里!!!史上最通俗易懂的Promise!!!
- 一起学习造轮子(一):从零开始写一个符合Promises/A+规范的promise
- 100 行代码实现 Promises/A+ 规范
- Promise实现原理(附源码)
- Promise 必知必会(十道题)
- BAT前端经典面试问题:史上最最最详细的手写Promise教程
最后
欢迎纠错,看到会及时修改哒!❤
温故而知新,希望我们都可以保持本心,念念不忘,必有回响。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!