一文了解Promise使用与实现
前言
Promise 作为一个前端必备技能,不管是从项目应用还是面试,都应该对其有所了解与使用。
常常遇到的面试五连问:
- 说说你对 Promise 理解?
- Promise 的出现解决了什么问题?
- Promise 有哪些状态?
- 你用过 Promise 哪些方法?
- 如何实现一个 Promise ?
什么是 Promise?
Promise 是异步编程的一种解决方案:从语法上讲,promise 是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。
Promise 有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态);状态一旦改变,就不会再变。创造 Promise 实例后,它会立即执行。
一般来说我们会碰到的回调嵌套都不会很多,一般就一到两级,但是某些情况下,回调嵌套很多时,代码就会非常繁琐,会给我们的编程带来很多的麻烦,这种情况俗称——回调地狱。
这时候我们的Promise 就应运而生、粉墨登场了。
Promise 的基本使用
Promise 是一个构造函数,自己身上有 all
、reject
、resolve
这几个眼熟的方法,原型上有 then
、catch
等同样很眼熟的方法。
首先创建一个 new Promise
实例
let p = new Promise((resolve, reject) => {
// 可以做一些异步操作,例如发送AJAX请求,这里使用 setTimeout 模拟
setTimeout(() => {
let num = Math.ceil(Math.random() * 10); // 生成 1-10 随机数
if (num <= 5) {
resolve('成功');
} else {
reject('失败');
}
}, 2000);
});
Promise 的构造函数接收一个参数:函数,并且这个函数需要传入两个参数:
resolve:异步操作执行成功后的回调函数;
reject:异步操作执行失败后的回调函数;
then
链式操作的用法
p.then((data) => {
console.log('resolve', data); // 'resolve', '成功'
}, (err) => {
console.log('reject', err); // 'reject', '失败'
})
then 中传了两个参数,then 方法可以接受两个参数,第一个对应 resolve
的回调,第二个对应reject
的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到两种结果。
catch
的用法
我们知道Promise 对象除了then 方法,还有一个catch 方法,它是做什么用的呢?其实它和then 的第二个参数一样,用来指定reject 的回调。用法是这样:
p.then((data) => {
console.log('resolve', data); // 'resolve', '成功'
}).catch((err) => {
console.log('reject', err); // 'reject', '失败'
});
效果和写在 then
的第二个参数里面一样。不过它还有另外一个作用:在执行 resolve
的回调(也就是上面then 中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个 catch
方法中。请看下面的代码:
p.then((data) => {
console.log('resolve', data);
console.log(test);
}).catch((err) => {
console.log('reject', err);
});
// 当成功时先后打印
resolve 成功
reject ReferenceError: test is not defined
在 resolve
的回调中,我们 console.log(test)
,而 test
这个变量是没有被定义的。如果我们不用 Promise,代码运行到这里就直接在控制台报错了,不往下运行了,也就是说进到catch方法里面去了,而且把错误原因传到了 reject
参数中。即便是有错误的代码也不会报错了,这与我们的 try/catch
语句有相同的功能。
finally
的用法
finally
方法返回一个 Promise。在 promise 结束时,无论结果是 fulfilled 或者是 rejected ,都会执行指定的回调函数。这为在 Promise
是否成功完成后都需要执行的代码提供了一种方式。
这避免了同样的语句需要在 then
和 catch
中各写一次的情况。
// 加载 loading
let isLoading = true;
// 假装模拟AJAX请求
function myRequest(){
return new Promise((resolve, reject) => {
setTimeout(() => {
let num = Math.ceil(Math.random() * 10); // 生成 1-10 随机数
if (num <= 5) {
resolve('成功');
} else {
reject('失败');
}
}, 1000);
})
}
myRequest().then(function(data) { console.log(data); })
.catch(function(error) { console.log(error); })
.finally(function() {
// 关闭loading
isLoading = false;
console.log('finally');
});
resolve
的用法
Promise.resolve(value)
方法返回一个以给定值解析后的 Promise 对象。如果这个值是一个 promise ,那么将返回这个 promise ;如果这个值是 thenable(即带有"then" 方法),返回的promise会“跟随”这个 thenable 的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。
// 示例 1 基本使用
const p = Promise.resolve("Success");
p.then(function(value) {
console.log(value); // "Success"
}, function(value) {
// 不会被调用
});
// 示例 2 resolve 一个数组
var p = Promise.resolve([1,2,3]);
p.then(function(arr) {
console.log(arr[0]); // 1
});
// 示例 3 resolve 另一个Promise
let original = Promise.resolve(33);
let cast = Promise.resolve(original);
cast.then(function(value) {
console.log('value: ' + value);
});
console.log('original === cast ? ' + (original === cast));
/*
* 打印顺序如下,这里有一个同步异步先后执行的区别
* original === cast ? true
* value: 33
*/
// 示例 4 resolve thenable 并抛出错误
// Resolve一个thenable对象
const p1 = Promise.resolve({
then: function(onFulfill, onReject) { onFulfill("fulfilled!"); }
});
console.log(p1 instanceof Promise) // true, 这是一个Promise对象
p1.then(function(v) {
console.log(v); // 输出"fulfilled!"
}, function(e) {
// 不会被调用
});
// Thenable在callback之前抛出异常
// Promise rejects
const thenable = { then: function(resolve) {
throw new TypeError("Throwing");
resolve("Resolving");
}};
const p2 = Promise.resolve(thenable);
p2.then(function(v) {
// 不会被调用
}, function(e) {
console.log(e); // TypeError: Throwing
});
// Thenable在callback之后抛出异常
// Promise resolves
const thenable = { then: function(resolve) {
resolve("Resolving");
throw new TypeError("Throwing");
}};
const p3 = Promise.resolve(thenable);
p3.then(function(v) {
console.log(v); // 输出"Resolving"
}, function(e) {
// 不会被调用
});
reject
的用法
Promise.reject()
方法返回一个带有拒绝原因的Promise
对象。
Promise.reject(new Error('fail')).then(function() {
// not called
}, function(error) {
console.error(error); // Stacktrace
});
all
的用法
谁跑的慢,以谁为准执行回调。all 接收一个数组参数,里面的值最终都算返回 Promise 对象。
Promise.all
方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。看下面的例子:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
let num = Math.ceil(Math.random() * 10); // 生成 1-10 随机数
if (num <= 5) {
resolve('成功');
} else {
reject('失败');
}
}, 1000);
});
let pAll = Promise.all([p, p, p]);
pAll.then((data) => {
console.log(data); // 成功时打印: ['成功', '成功', '成功']
}, (err) => {
console.log(errs); // 只要有一个失败,就会返回当前失败的结果。 '失败'
})
有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。在这里可以解决时间性能的问题,我们不需要在把每个异步过程同步出来。
all 缺点就是只要有一个任务失败就会都失败。
allSettled
的用法
该 Promise.allSettled
****方法返回一个在所有给定的 promise 都已经fulfilled
或rejected
后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。
当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise
的结果时,通常使用它。
相比之下,Promise.all
更适合彼此相互依赖或者在其中任何一个reject
时立即结束。
const p1 = Promise.resolve(3);
const p2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
Promise.allSettled([p1, p2]).
then((results) => results.forEach((result) => console.log(result.status)));
// 执行后打印
// "fulfilled"
// "rejected"
any
的用法
Promise.any
接收一个 promise
可迭代对象,只要其中的一个 promise
成功,就返回那个已经成功的 promise
。如果可迭代对象中没有一个 promise
成功(即所有的 promises
都失败/拒绝),就返回一个失败的 promise
和 AggregateError
类型的实例,它是 Error
的一个子类,用于把单一的错误集合在一起。本质上,这个方法和 Promise.all
是相反的。
const pErr = new Promise((resolve, reject) => {
reject("总是失败");
});
const pSlow = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "最终完成");
});
const pFast = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "很快完成");
});
Promise.any([pErr, pSlow, pFast]).then((value) => {
console.log(value); // '很快完成'
})
race
的用法
Promise.race
****方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise就会解决或拒绝。也可理解 谁跑的快,以谁为准执行回调。
race
的使用场景:比如我们可以用race 给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:
// 假设请加某个图片资源
function requestImg() {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = function() {
resolve(img);
}
// 尝试输入假的和真的链接
img.src = '**';
})
}
// 延时函数,用于给请求计时
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求超时');
}, 5000)
})
}
Promise.race([requestImg(), timeout()]).then((data) => {
console.log(data); // 成功时 img
}).catch((err) => {
console.log(err); // 失败时 "请求超时"
})
如何实现一个Promise
1、创建一个 MyPromise 类,传入 executor(执行器)并添加 resolve 和 reject 方法
class MyPromise {
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
executor(this.resolve, this.reject)
}
// 更改成功后的状态
resolve = () => {}
// 更改失败后的状态
reject = () => {}
}
2、添加状态和 resolve、reject 事件处理
// 定义三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor){
executor(this.resolve, this.reject)
}
// 储存状态,初始值是 pending
status = PENDING;
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// 更改成功后的状态
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
}
// 更改失败后的状态
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
}
}
}
3、.then 的实现
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value); // 调用成功回调,并且把值返回
} else if (this.status === REJECTED) {
onRejected(this.reason); // 调用失败回调,并且把原因返回
}
}
上面三步已经简单实现了一个 Promise
我们先来调用试试:
const promise = new MyPromise((resolve, reject) => {
resolve('success')
reject('err')
})
promise.then(value => {
console.log('resolve', value)
}, reason => {
console.log('reject', reason)
})
// 打印结果:resolve success
经过测试发现如果使用异步调用就会出现顺序错误那么我们怎么解决呢?
4、实现异步处理
// 定义三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
// 成功存放的数组
onResolvedCallbacks = [];
// 失败存放法数组
onRejectedCallbacks = [];
// 储存状态,初始值是 pending
status = PENDING;
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// 更改成功后的状态
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onRejectedCallbacks.forEach((fn) => fn()); // 调用成功异步回调事件
}
}
// 更改失败后的状态
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallback.forEach((fn) => fn()); // 调用失败异步回调事件
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value); //调用成功回调,并且把值返回
} else if (this.status === REJECTED) {
onRejected(this.reason); // 调用失败回调,并且把原因返回
} else if (this.status === PENDING) {
// onFulfilled传入到成功数组
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value);
})
// onRejected传入到失败数组
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
})
}
}
}
修改后通过调用异步测试没有
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000);
})
promise.then(value => {
console.log('resolve', value)
}, reason => {
console.log('reject', reason)
})
// 等待 2s 输出 resolve success
但是如果使用链式调用 .then
就会发现有问题,而原生的 Promise 是支持的。 那么我们如何支持呢?
链式调用示例:
const promise = new MyPromise((resolve, reject) => {
resolve('success')
})
function other () {
return new MyPromise((resolve, reject) =>{
resolve('other')
})
}
promise.then(value => {
console.log(1)
console.log('resolve', value)
return other()
}).then(value => {
console.log(2)
console.log('resolve', value)
})
// 第二次 .then 将会失败
5、实现 .then
链式调用
class MyPromise {
...
then(onFulfilled, onRejected) {
// 为了链式调用这里直接创建一个 MyPromise,并 return 出去
return new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
const x = onFulfilled(this.value);
resolvePromise(x, resolve, reject);
} else if (this.status === REJECTED) {
onRejected(this.reason);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
})
}
}
function resolvePromise(x, resolve, reject) {
// 判断x是不是 MyPromise 实例对象
if (x instanceof MyPromise) {
// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
x.then(resolve, reject)
} else {
resolve(x)
}
}
6、也可以加入 try/catch
容错
...
constructor(executor) {
try {
executor(this.resolve, this.reject)
} catch(error) {
this.reject(error)
}
}
最后
本文就先到这里了,后续有时间再补充
.alL
、.any
等其他方法的实现。相关推荐
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises作者:雨中愚
链接:https://juejin.cn/post/6995923016643248165
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
一文了解Promise使用与实现的更多相关文章
- Promise 异步(asynchronous )编程
概述 Promise.all(iterable) 方法返回一个promise,该promise会等iterable参数内的所有promise都被resolve后被resolve,或以第一个promis ...
- Promise杂记
更好的阅度体验 前言 API Promise特点 状态跟随 V8中的async await和Promise 实现一个Promise 参考 前言 作为一个前端开发,使用了Promise一年多了,一直以来 ...
- promise待看文档备份
http://swift.gg/2017/03/27/promises-in-swift/ http://www.cnblogs.com/feng9exe/p/9043715.html https:/ ...
- Javascript - Promise学习笔记
最近工作轻松了点,想起了以前总是看到的一个单词promise,于是耐心下来学习了一下. 一:Promise是什么?为什么会有这个东西? 首先说明,Promise是为了解决javascript异步编 ...
- ABP文档 - Javascript Api - AJAX
本节内容: AJAX操作相关问题 ABP的方式 AJAX 返回信息 处理错误 HTTP 状态码 WrapResult和DontWrapResult特性 Asp.net Mvc 控制器 Asp.net ...
- 深入理解jQuery、Angular、node中的Promise
最初遇到Promise是在jQuery中,在jQuery1.5版本中引入了Deferred Object,这个异步队列模块用于实现异步任务和回调函数的解耦.为ajax模块.队列模块.ready事件提供 ...
- 大白话讲解Promise(二)理解Promise规范
上一篇我们讲解了ES6中Promise的用法,但是知道了用法还远远不够,作为一名专业的前端工程师,还必须通晓原理.所以,为了补全我们关于Promise的知识树,有必要理解Promise/A+规范,理解 ...
- ready是先执行的,load后执行,DOM文档的加载步骤
在jq中在文档载入完毕后有这几种方式去执行指定函数: $(document).ready(function() { // ...代码... }); //document ready 简写 $(func ...
- 【转】Unity中的协同程序-使用Promise进行封装(三)
原文:http://gad.qq.com/program/translateview/7170967 译者:崔国军(飞扬971) 审校:王磊(未来的未来) 在这个系列的最后一部分文章,我们要通过 ...
随机推荐
- pwnable.kr之simple Login
pwnable.kr之simple Login 懒了几天,一边看malloc.c的源码,一边看华庭的PDF.今天佛系做题,到pwnable.kr上打开了simple Login这道题,但是这道题个人觉 ...
- Python包安装过程
以下是paramiko-1.7.7.1的安装过程,可以看到整个过程分为步,第一步是build,就是拷贝源文件到build文件夹里, F:\VMFiles\tmpFiles\paramiko-1.7.7 ...
- Create Virtual Network with Virtualbox
Create a virtual machine "ubs1" with ubuntu server 12.04, set its network as Host-only; St ...
- MyBatis-Plus QueryWrapper及LambdaQueryWrapper的使用
转载自 MyBatis-Plus QueryWrapper及LambdaQueryWrapper的使用 LambdaQueryWrapper https://blog.csdn.net/lt32603 ...
- SpringBoot开发十二-账号设置
需求介绍-账号设置 账号设置里面的上传头像(文件) 首先请求必须是一个 POST 请求,其次表单的属性 enctype = "multipart/form-data" 然后就是利用 ...
- 微信小程序开发(二)——使用WeUI组件库
一.前言 因为小程序的api描述都比较简单,并没有wxml及wxss的描述,一定会想小程序有没有一个UI库,类似于前端中的Bootstrap,MD,Semantic UI这样的框架UI库.有的,它就是 ...
- NOIP 模拟 $29\; \rm 最长不下降子序列$
题解 \(by\;zj\varphi\) 观察这个序列,发现模数很小,所以它的循环节很小. 那么可以直接在循环节上做最长上升子序列,但是循环节中的逆序对会对拼接后的答案造成影响. 没有必要找逆序对个数 ...
- ffmpeg第6篇:滤镜语法
前言 哈哈,回来继续填坑了,前段时间较忙没时间写,现在继续~ 简介 滤镜是ffmpeg的一个很强大的功能,它支持许多有用的视频处理功能,常见的滤镜如:缩放.旋转.水印.裁剪等 一个比较经典的滤镜使用方 ...
- vue同一个页面可以有多个router-view
参考:https://blog.csdn.net/u011615787/article/details/80075240 参考:https://router.vuejs.org/zh/guide/es ...
- spring4整合hibernate5以及出现的问题解决办法
每一次的学习,都是一小步一小步的进行的,学习语言,重要的是能把hello world写出来 以及在学习过程中出现的问题能够及时的记录并总结 spring目前最新的版本是4.3,而hibernate是5 ...