start
- Promise中文释义: 承诺。
- Promise这个单词截止到现在,番茄都不知道敲过多少次了,工作中经常会使用到,但是我觉得我对它掌握程度不够熟练。
- 今天再来理解理解Promise,然后手撕一个出来。YES,祝我好运!
- 学习Promise之前,建议JS基础要打好,基本的
原型
,this指向
,new
都需要掌握。
1.Promise是什么?
1.1 概念
Promise是异步编程的一种解决方案,比传统的解决方案:“回调函数和事件” 更合理和更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
1.2 粗略的理解一下Promise
抽象理解
-
Promise 是一门新的技术(ES6 规范)
-
Promise 是 JS 中进行异步编程的新解决方案
备注:旧方案是单纯使用回调函数
具体表达
- 从语法上来说: Promise 是一个构造函数
- 从功能上来说: Promise 对象用来封装一个异步操作并可以获取其成功/ 失败的结果值
2.为什么要是用Promise?
2.1 异步编程的常见场景
- 定时器
ajax
请求nodejs
的文件操作模块(fs模块) ==>读取文件时是异步的。
1.定时器
// 浏览器环境
setTimeout(function () {
console.log('1.定时器')
}, 2000)
2.ajax请求
// 浏览器环境
const xhr = new XMLHttpRequest()
xhr.open('get', 'http://www.baidu.com')
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('2.ajax请求',xhr.status)
} else {
console.log('请求失败: status: ' + xhr.status)
}
}
}
3.nodejs中读取文件
// nodejs环境
const fs = require('fs');
fs.readFile('1.promise.md', (err, data) => {
// 如果出错 则抛出错误
if (err) throw err;
//输出文件内容
console.log('3.nodejs中读取文件',data.toString());
});
对
throw
、try
、catch
不了解的可以看下JS错误处理的博客
2.2 旧的处理方案(单纯使用回调函数)
- 就以读取文件为例,如果我们只读取一个文件,ok没问题,很简单。
const fs = require('fs');
fs.readFile('1.promise.md', (err, data) => {
// 如果出错 则抛出错误
if (err) throw err;
//输出文件内容
console.log(data.toString());
});
- 假如我希望,在读取完第一个文件之后读取第二个文件,依次类推,直到读取到最后一个文件。
const fs = require('fs');
// 1.读取第一个文件
fs.readFile('1.promise1.md', (err, data) => {
if (err) throw err;
console.log(data.toString());
// 2.读取第二个文件
fs.readFile('1.promise2.md', (err, data) => {
if (err) throw err;
console.log(data.toString());
// 3.读取第三个文件
fs.readFile('1.promise3.md', (err, data) => {
if (err) throw err;
console.log(data.toString());
});
});
});
由于需求必须等前面一个文件读取完,再读取后面一个文件。所以目前只能把读取后面文件的代码,写在前一个读取文件的回调函数中。写到这里,问题就很明显了:
- 我现在是读取三个文件,假如这个文件数量很多怎么办?嵌套的括号层级肯定会非常多。远看一下代码的代码结构就类似
>
这种结构了,这就叫做回调地狱。这样就导致阅读起来会非常难受!- 假如读取过程中某一个地方出错了,我们如何快速处理异常?
- 回调函数必须提前写好,不是很方便。
2.3 为什么使用Promise?
1-指定回调函数的方式更加灵活
- 旧的: 必须在启动异步任务前指定
- Promise: 启动异步任务 => 返回
Promie
对象 => 给Promise对象绑定回调函 数(甚至可以在异步任务结束后指定/多个)
2-支持链式调用, 可以解决回调地狱问题
- 旧的:回调地狱,不便于阅读,不便于异常处理
- Promise链式调用,来解决回调地狱问题,回到方式
3.async awiat
- Promise只是简单的改变格式,并没有彻底解决上面的问题真正要解决上述问题,
promise +async/await
实现异步传同步。
这个在以后的
await
和async
的博客再细说。
3. Promise怎么用?
ps:我第一次看到它的用法感觉很难,现在再看它,其实很简单,加油共勉。
1.犹豫不决就控制台打印一下!
// 打印出来 Promise 它是一个方法,我们换个方式继续打印
console.log(Promise)
// `console.dir` 可以打印一个对象所有的属性和方法。
//(ps:由上面的console.log,我们知道它是一个函数。函数本身也是一个特殊的对象,我们打印一下看看它有哪些属性和方法)
console.dir(Promise)
①:可以得知Promise本身是一个函数;
②:这个函数对象本身有一些自己的方法,例如:(all
,race
,resolve
,reject
)
③:它的portotype
(显式原型)上除了默认的constructor
,还有一些独有的方法,例如:(then
,catch
,finally
)
2. 去MDN官网瞅瞅
进去一脸懵逼,出来一脸懵逼 (/ω\)…
3.看看有没有别的教程
4.开始使用
看了上面的博客我们可以知道Promise的基本用法:
const p = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
console.log(p)
解读一下上面的代码
- 我们用
console
打印的时候,已知Promise是一个函数。这里的基本用法是new
了一下它,ok ,它是一个构造函数。 - 构造函数就构造函数,见得多了,比如我们创建一个对象 可以:
var obj = new Object()
。但是为什么我第一次见到它,就潜意识觉得它很难呢,其实就是它这个构造函数,传入了一个匿名函数作为了实参!!
我把代码改吧改吧,让我们理解起来简单点
const p = new Promise(
// 1.我 new Promise的时候,传入了一个匿名函数进去,仅此而已
function (resolve, reject) {
// 2.这里面可以做一些逻辑处理 同步异步皆可
// ... some code
// if (/* 异步操作成功 */) {
// resolve(value);
// } else {
// reject(error);
// }
}
);
console.log(p)
- 它这个匿名函数呢,也有两个形参,一个
resolve
, 一个reject
,
- 直接在这两个形参后面加了括号,加了括号代表什么? 加了括号表示 执行
- 什么代码能执行,函数啊!
- 这两个函数还传入了实参
value
、error
- 那我们把代码改成这样的,去运行一下尝试一下。
// 1. 依据 构造函数:Promise,new了一个实例p
const p = new Promise(
// 2.在new Promise的时候,传入了一个匿名函数作为参数
function (resolve, reject) {
// 3.这里为了了解这个匿名函数的参数有什么用,我这里执行一下它
resolve('lazy');
reject('tomato');
}
);
console.log('p是什么:',p)
- 运行截图
- 截图中的这个
[[PromiseState]]
是什么?
- 括起来的双方括号
[[PromiseState]]
表示它是一个内部属性,不能在代码中直接访问。- 我之前纠结过这个地方,很好奇为什么会有这种形式的属性,查询了官方ECMA的文档。我个人简单理解,就是这个对象有一个内部属性,代码无法访问也无法定义这种属性。
- 其次按照英译理解一下这个属性,
[[PromiseState]]
就是这个实例的状态
5. p(Promise实例)的状态
-
实例对象p中的一个属性
[[PromiseState]]
,它的值有三种情况,-
pending
未决定的 -
resolved
/fullfilled
成功 -
rejected
失败
State => 状态
,PromiseState=> Promise的状态
-
-
实例对象p(Promise实例)的 状态改变 有哪几种情况
- 由默认的
pending
变为resolved
- 由默认的
pending
变为rejected
说明: 只有这 2 种, 且一个 promise 对象只能改变一次 无论变为成功还是失败, 都会有一个结果数据 成功的结果数据一般称为 value, 失败的结果数据一般称为 reason
- 由默认的
-
如何改变 实例对象p(Promise实例) 的状态
在new Promise()
传入的匿名函数中,以下情况,会改变实例对象的状态。resolve(value)
: 如果当前状态是pending
就会变为resolved
reject(reason)
: 如果当前状态是pending
就会变为rejected
- 抛出异常: 如果当前状态是
pending
就会变为rejected
6. 实例的 then()
方法
实例的then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。
7.开始使用Promise
同步的 Promise
// 说下执行顺序
const p = new Promise( // 1
function (resolve, reject) { // 2
resolve('lazy'); // 3
console.log(1) // 4
reject('tomato'); // 实例的状态已经改变了,这里不会再改变状态了
}
);
// 5 这里注意一下,状态改变 先与 绑定回调函数
p.then(res=>{
console.log(res) // 6
},err=>{
console.log(err)
})
// 1
// lazy
异步下的 Promise
const p = new Promise( // 1
function (resolve, reject) {
console.log('hello') // 2
setTimeout(function () { // 3
reject('tomato'); // 5
}, 2000)
}
);
// 4 这里注意一下,绑定回调函数 先与 状态改变
p.then(res => {
console.log(res)
}, err => {
console.log('1',err)
})
// hello
// '1' tomato
4.promise实例上的方法
1.then
代码示例
const p = new Promise(
function (resolve, reject) {
setTimeout(function () {
resolve('tomato');
}, 2000)
}
);
p.then(res => {
console.log('1',res)
}, err => {
console.log('2',err)
})
// 1 tomato
注意事项
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved
时调用,第二个回调函数是Promise对象的状态变为rejected
时调用。- 两个函数都是可选的,不一定要提供。
then
方法返回的是一个新的Promise实例
2.catch
代码示例
const p = new Promise(
function (resolve, reject) {
setTimeout(function () {
reject('tomato');
}, 2000)
}
);
p.catch(err => {
console.log(err)
})
// tomato
注意事项
catch
方法可以接受一个回调函数作为参数。回调函数触发的方式有:
Promise对象的状态变为
rejected
时调用。
then()
方法指定的回调函数,如果运行中抛出错误,也会被catch()
方法捕获。new实例中的匿名函数运行报错,也会进catch
catch
方法返回的也是一个新的Promise实例
catch
就是基于then
方法变化来的:
.then(null, rejection)
或.then(undefined, rejection)
实现的
3.finally
代码
// 1.finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
// finally本质上是then方法的特例。
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
5.Promise构造函数上的方法
1. Promise.all()
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
Promise.all(
[
Promise.resolve('2'),
Promise.resolve('3'),
Promise.resolve('4')
]
)
上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
2. Promise.race()
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
const p = Promise.race([
2,
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
console.log(p)
Promise.race()方法的参数与Promise.all()方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理。
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
3. Promise.allSettled()
Promise.all()方法只适合所有异步操作都成功的情况,如果有一个操作失败,就无法满足要求。
为了解决这个问题,ES2020 引入了Promise.allSettled()方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了”fulfilled“和”rejected“两种情况。
返回值的状态只可能为成功;
返回值的结果呢是根据传递来的数据依次排序的,其次成功则status=fulfilled,失败则status=rejected
async function tomato() {
let p = await Promise.allSettled(
['2',
new Promise(function (resolve, reject) {
reject('error')
setTimeout(() => reject('error'), 5000)
})]
)
console.log(p)
/*
PromiseState:'fulfilled'
PromiseResult:[
{
"status": "fulfilled",
"value": "2"
},
{
"status": "rejected",
"reason": "error"
}
]
*/
}
tomato()
4. Promise.any()
ES2021 引入了Promise.any()方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。
5. Promise.resolve()
作用:
将现有对象转为 Promise 对象
Promise.resolve()方法的参数分成四种情况。
(1)参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
(2)参数是一个thenable对象
thenable对象指的是具有then方法的对象,比如下面这个对象。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。
(3)参数不是具有then()方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。
const p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s)
});
// Hello
(4)不带有任何参数
Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。
6. Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。
相关博客
参考博客
end
- 写到这里我感觉 Promise就是一个构造函数,显式原型上和函数本身有一些方法。用来解决异步问题。当然啊要结合
async await
用起来也很舒服。 - 不过如此,当然后面最好看看面试题怎么考的,后续写写这里。