Promise出现的背景
本文已在掘金、博客园、CSDN多平台发布,转载需署名
- 同步:需要等待一件事情完成后才能做下一件事。
- 异步:一件事情在等待执行的过程中,我们可以继续做下一件事,不需要等待上一件事完成。
常见的异步场景:定时器setTimeout
事件绑定回调函数数据请求Ajax
/ 请求图片…
众所周知,js
执行的时候,一次只能执行一个任务,它会阻塞其他任务。由于这个缺陷导致js
的所有网络操作
,浏览器事件
,都必须是异步执行。而异步执行可以使用回调函数
执行。
而Promise
是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6
将其写进了语言标准,统一了用法,原生提供了Promise
对象。
Promise的规范
Promise
必须遵循一组特定的规则:
Promise
或者"thenable"
必须是一个提供符合标准的.then()
方法的对象- 一个
pendding
状态的Promise
会转换为fulfilled
状态或rejected
状态 Promise
对象一旦变为fulfilled
状态或rejected
状态,则状态永远不会再次发生改变- 一旦
Promise
执行完成后,一定会产生一个值(这个值可能是undefined
)。并且当前值永远不会被更改。
这里的值无法被修改
Promise的基本使用
ES6
规定,Promise
对象是一个构造函数,用来生成Promise
实例.
console.dir(Promise)
我们可以发现,Promise
是一个构造函数,有all
、reject
、resolve
这几个常用的方法,原型上有then
、catch
等同样很眼熟的方法。因此用Promise
构造函数 new
出来的实例对象就会拥有then
、catch
方法。
在了解promise
的基本流程前,先要知道 promise
的一些基本属性
1、promise 的状态⭐
promise
的状态是 promise
实例对象中的一个属性 [PromiseState]
pending
进行中resolved
/fulfilled
成功rejected
失败
状态只能由
Pending
变为Fulfilled
或由Pending
变为Rejected
,且状态改变之后不会在发生变化,会一直保持这个状态。
(源码里通过判断PromiseState
状态和PromiseRusult
的值来决定then
方法中回调函数和异步任务的执行顺序)
2、promise 对象的值
实例对象中的另一个属性 [PromiseResult]
保存着异步任务 [成功/失败] 的结果
resolve
reject
3、promise 的基本流程
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
var promise = new Promise(function(resolve,reject){
if(/* 异步操作成功 */){
resolve(value);
}else{
reject(error);
}
});
resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending
变为 resolved
),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending
变为 rejected
),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
resolve的使用
例如:
var p = new Promise(function(resolve, reject){
// 模拟异步操作
setTimeout(function(){
console.log('异步任务执行完成了');
resolve('ok');
}, 3000);
});
在上面的代码中,我们用定时器setTimeout
模拟了一个异步操作,3秒后,输出“异步任务执行完成了
”,并且调用resolve
方法。
有没有观察到p
这个实例对象并没有执行p()
去调用它,传入的函数却已经执行了呢?
注意:
Promise(fn)
:传递的fn
函数是一个同步函数,会立刻执行,通过new
关键字去调用Promise
构造函数的时候就会自动立即调用fn
函数(详情看源码)
因此我们用Promise
的时候一般是包在一个函数中,在需要的时候去运行这个函数,如:
function testAsync(){
var p = new Promise(function(resolve, reject){
// 模拟异步操作
setTimeout(function(){
console.log('异步任务执行完成了');
resolve('ok');
}, 3000);
});
return p;
}
testAsync()
3秒后,输出“异步任务执行完成了
”,并且在testAsync
函数最后,会return
出一个Promise
对象,还记得Promise
对象上有then
、catch
方法吧?
所以我们可以:
testAsync().then(function(res){
//可以直接拿到返回的数据res
console.log(res);
//后面可以用传过来的数据res做些其他操作
//......
});
显然,Promise
的优势在于,可以在then
方法中继续写Promise
对象并返回,然后继续调用then
链式调用来进行回调操作。避免了回调地狱
的问题
回调地狱:
then链式调用:
functionsendRequest(url) {
// url 为请求路径
returnnewPromise((resolve, reject) => {
// 定时器模拟网络请求
setTimeout(() => {
if (url.includes('user')) {
resolve(url)
}
else {
reject('请求错误')
}
}, 1000);
})
}
// 因为函数的返回值是Promise对象所以可采用链式调用
sendRequest("/user").then(res=> {
return sendRequest(`userlist ${res}`)
}).then(res=> {
returnsendRequest(`userdetail ${res}`)
}).then(res=> {
console.log(res)
})
reject的使用
事实上,我们前面的例子都是只有“执行成功”
的回调,还没有“失败
”的情况,reject
的作用就是把romise
的状态从pending
置为rejected
,这样我们在then
中就能捕获到,然后执行“失败
”情况的回调。看下面的代码:
function getNumber(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
var num = Math.ceil(Math.random()*100); //生成1-100的随机数
if(num<=50){
resolve(num);
}
else{
reject('数字太大了');
}
}, 2000);
});
return p;
}
getNumber()
.then(
function(data){
console.log('resolved');
console.log(data);
},
function(reason, data){
console.log('rejected');
console.log(reason);
}
);
getNumber
函数用来异步获取一个数字,2秒后执行完成,如果数字小于等于50,我们认为是“成功”了,调用resolve
修改Promise
的状态。否则我们认为是“失败
”了,调用reject并传递一个参数
,作为失败的原因。
运行getNumber
并且在then
中传了两个参数,then
方法可以接受两个参数,第一个对应resolve
的回调,第二个对应reject
的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到下面两种结果:
catch的使用
Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
catch
相当于then
的第二个参数,用来指定reject
的回调,用法如下:
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});
效果和写在then
的第二个参数里面一样。不过它还有另外一个作用:在执行resolve
的回调(也就是上面then
中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js
,而是会进到这个catch
方法中。请看下面的代码:
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
console.log(somedata); //此处的somedata未定义
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});
在resolve
的回调中,我们console.log(somedata);
而somedata
这个变量是没有被定义的。如果我们不用Promise
,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果:
resolved
4
rejected
ReferenceError:somedata is not defined(...)
也就是说进到catch
方法里面去了,而且把错误原因传到了reason
参数中。即便是有错误的代码也不会报错了,这与我们的try/catch
语句有相同的功能。
finally的使用
finally()
方法用于指定不管 Promise
对象最后状态如何,都会执行的操作。该方法是 ES2018
引入标准的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
上面代码中,不管promise
最后的状态,在执行完then
或catch
指定的回调函数以后,都会执行finally
方法指定的回调函数。
finally
方法的回调函数不接受任何参数,这意味着没有办法知道,前面的Promise
状态到底是fulfilled
还是rejected
。这表明,finally
方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
all的使用
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
Promise
的all
方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。我们仍旧使用定义好的testAsync1、testAsync2、testAsync3这三个函数,看下面的例子:
function testAsync1(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
console.log('1异步任务执行完成了');
resolve('数据1');
}, 1000);
});
return p;
}
function testAsync2(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('2异步任务执行完成了');
resolve('数据2');
}, 2000);
});
return p;
}
function tsetAsync3(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('3异步任务执行完成了');
resolve('数据3');
}, 2000);
});
return p;
}
Promise
.all([testAsync1(), testAsync2(), testAsync3()])
.then(function(results){
console.log(results);
});
用Promise.all
来执行,all
接收一个数组参数,里面的值最终都是返回Promise
对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then
里面。,all
会把所有异步操作的结果放进一个数组中传给then
,就是上面的results
。所以上面代码的输出结果就是:
promise 实践练习-AJAX请求
// 原生
const btn = document.querySelector('#btn');
btn.addEventListener("click", () => {
// 创建对象
const xhr = new XMLHttpRequest();
// 初始化
xhr.open('GET', 'https://api.apiopen.top/getTime');
// 发送
xhr.send();
// 处理响应结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 判断响应状态码
if (xhr.status >= 200 && xhr.status < 300) {
// 控制台输出响应体
console.log(xhr.response);
} else {
// 控制台输出状态码
console.log(xhr.status);
}
}
}
})
// promise 封装
btn.addEventListener("click", () => {
// 创建 Promise
const promise = new Promise((resolve, reject) => {
// 创建对象
const xhr = new XMLHttpRequest();
// 初始化
xhr.open('GET', 'https://api.apiopen.top/getTime');
// 发送
xhr.send();
// 处理响应结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 判断响应状态码
if (xhr.status >= 200 && xhr.status < 300) {
// 控制台输出响应体
resolve(xhr.response);
} else {
// 控制台输出状态码
reject(xhr.status);
}
}
}
})
promise.then(result => {
console.log(result);
}).catch(err => {
console.log(err);
})
})
手写Promise源码
1、定义整体结构
创建两个文件 index.html
,promise.js
在promise.js
写最基本的 promise
结构
function Promise(executor) {
// new调用的时候就会立刻执行同步函数executor
}
// 添加 then 方法
Promise.prototype.then = function(onResolved, onRejected) {
}
index.html
里引入 我们刚写的 promise.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入 promise -->
<script src="./promise.js"></script>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
resolve('Ok');
})
p.then(res => {
console.log(res);
}, reason => {
console.log(reason);
})
</script>
</body>
</html>
2、封装 resolve 和 reject 结构
promise.js
里的 代码
function Promise(executor) {
// resolve 函数
function resolve(data) {
}
// reject 函数
function reject(data) {
}
// 同步调用 [执行器函数]
executor(resolve, reject);
}
3、resolve 和 reject 代码的实现
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = null;
// 保存实例对象的 this 的值
const that = this;
// resolve 函数
function resolve(data) {
// 1. 修改对象的状态 (promiseState)
that.PromiseState = 'fulfilled';
// 2. 设置对象结果值 (promiseResult)
that.PromiseResult = data;
}
// reject 函数
function reject(data) {
// 1. 修改对象的状态 (promiseState)
that.PromiseState = 'rejected';
// 2. 设置对象结果值 (promiseResult)
that.PromiseResult = data;
}
// 同步调用 [执行器函数]
executor(resolve, reject);
}
// 添加 then 方法
Promise.prototype.then = function(onResolved, onRejected) {
}
4、throw 抛出异常改变状态
try {
// 同步调用 [执行器函数]
executor(resolve, reject);
} catch (error) {
// 修改 promise 对象状态为 失败
reject(error)
}
5、promise 对象状态只能修改一次
// resolve 函数
function resolve(data) {
// 来个if判断一下就行
if (that.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
that.PromiseState = 'fulfilled';
// 2. 设置对象结果值 (promiseResult)
that.PromiseResult = data;
}
// reject 函数
function reject(data) {
// 判断状态
if (that.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
that.PromiseState = 'rejected';
// 2. 设置对象结果值 (promiseResult)
that.PromiseResult = data;
}
6、then方法执行回调
// 添加 then 方法
Promise.prototype.then = function(onResolved, onRejected) {
// 调用回调函数 PromiseState
if (this.PromiseState === 'fulfilled') {
onResolved(this.PromiseResult);
}
if (this.PromiseState === 'rejected') {
onRejected(this.PromiseResult);
}
}
待更新…