start
- Promise中文释义: 承诺。
- Promise这个单词截止到现在,番茄都不知道敲过多少次了,工作中经常会使用到,但是我觉得我对它掌握程度不够熟练。
- 今天再来理解理解Promise,然后手撕一个出来。YES,祝我好运!
- 学习Promise之前,建议JS基础要打好,基本的
原型 ,this指向 ,new 都需要掌握。
1.Promise是什么?
1.1 概念
Promise是异步编程的一种解决方案,比传统的解决方案:“回调函数和事件” 更合理和更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
1.2 粗略的理解一下Promise
抽象理解
具体表达
- 从语法上来说: 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中读取文件
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');
fs.readFile('1.promise1.md', (err, data) => {
if (err) throw err;
console.log(data.toString());
fs.readFile('1.promise2.md', (err, data) => {
if (err) throw err;
console.log(data.toString());
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.犹豫不决就控制台打印一下!
console.log(Promise)
console.dir(Promise)
①:可以得知Promise本身是一个函数; ②:这个函数对象本身有一些自己的方法,例如:(all ,race ,resolve ,reject ) ③:它的portotype (显式原型)上除了默认的constructor ,还有一些独有的方法,例如:(then ,catch ,finally )
2. 去MDN官网瞅瞅
进去一脸懵逼,出来一脸懵逼 (/ω\)…
3.看看有没有别的教程
阮一峰的ES6_Promise教程
4.开始使用
看了上面的博客我们可以知道Promise的基本用法:
const p = new Promise(function(resolve, reject) {
if (){
resolve(value);
} else {
reject(error);
}
});
console.log(p)
解读一下上面的代码
- 我们用
console 打印的时候,已知Promise是一个函数。这里的基本用法是new 了一下它,ok ,它是一个构造函数。 - 构造函数就构造函数,见得多了,比如我们创建一个对象 可以:
var obj = new Object() 。但是为什么我第一次见到它,就潜意识觉得它很难呢,其实就是它这个构造函数,传入了一个匿名函数作为了实参!! 我把代码改吧改吧,让我们理解起来简单点
const p = new Promise(
function (resolve, reject) {
}
);
console.log(p)
- 它这个匿名函数呢,也有两个形参,一个
resolve , 一个reject ,
- 直接在这两个形参后面加了括号,加了括号代表什么? 加了括号表示 执行
- 什么代码能执行,函数啊!
- 这两个函数还传入了实参
value 、error
- 那我们把代码改成这样的,去运行一下尝试一下。
const p = new Promise(
function (resolve, reject) {
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(
function (resolve, reject) {
resolve('lazy');
console.log(1)
reject('tomato');
}
);
p.then(res=>{
console.log(res)
},err=>{
console.log(err)
})
异步下的 Promise
const p = new Promise(
function (resolve, reject) {
console.log('hello')
setTimeout(function () {
reject('tomato');
}, 2000)
}
);
p.then(res => {
console.log(res)
}, err => {
console.log('1',err)
})
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)
})
注意事项
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)
})
注意事项
-
catch 方法可以接受一个回调函数作为参数。 -
回调函数触发的方式有:
-
catch 方法返回的也是一个新的Promise实例 -
catch 就是基于then 方法变化来的:
.then(null, rejection) 或.then(undefined, rejection) 实现的
3.finally
代码
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
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)
}
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)
});
(4)不带有任何参数 Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。 所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。
6. Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。 Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。
相关博客
- 14_2.14_2.手写Promise
- 14_3.JS中的错误怎么处理? try catch finally throw
3.async await (晚点写)
参考博客
end
- 写到这里我感觉 Promise就是一个构造函数,显式原型上和函数本身有一些方法。用来解决异步问题。当然啊要结合
async await 用起来也很舒服。 - 不过如此,当然后面最好看看面试题怎么考的,后续写写这里。
|