浅谈Promise原理与应用
在JavaScript中,所有代码都是单线程。由于该“缺陷”,JavaScript在处理网络操作、事件操作时都是需要进行异步执行的。AJAX就是一个典型的异步操作
对于异步操作,有传统的利用回调函数和使用 Promise,二者的对比如下:
//以往回调方式
函数1(function(){
//代码执行...(ajax1) 函数2(function(){
//代码执行...(ajax2) 函数3(function(data3){
//代码执行...(ajax3)
});
...
});
}); //Promise回调方式:链式调用,可构建多个回调函数。
//例如请求一个ajax之后,需要这个拿到这个ajax的数据去请求下一个ajax
promise().then().then()...catch()
对比可知,使用传统回调函数方式处理异步操作很复杂。为了解决这样的问题,commonJS引入了Promise概念,很好的解决了JavaScript的异步操作。
概念
Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更简单易理解且实用,所以Promise简单来说就是一个容器,里面保存着某个未来才会执行事件(通常为一个异步操作)的结果。
特点
- 对象的状态不受外界影响
- 一旦状态改变,就不会再变化,任何时候都可以得到这个结果
语法
//创建Promise实例
let promise = new Promise( (resolve,reject) =>{
// 执行相应代码并根据情况调用resolve或者reject
...
} )
// 在promise的then方法中执行回调
Promise.then( function(){
// 第一个参数是返回resolve状态时执行的回调函数
},function(){
// 第二个参数是返回reject状态时执行的回调函数
} )
状态
Promise对象代表一个异步操作,有三种状态
pending(等待)、resolved(成功状态)、rejected(失败状态)
两种状态改变方式:pending => resolved,pending => rejected
注:只有异步操作的结果才可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
缺点
- 无法取消 Promise,一旦新建他就会立即执行,中途无法取消;
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部;
- 当处于 pending 状态时,无法得知目前进展到哪一阶段(刚刚开始还是即将完成)
用法 先看下面这个例子:
setTimeout( ()=>{
console.log('123');
},0 )
console.log('456');
执行结果相信大家都知道,上面console处于异步代码中(即使延迟为0),下面console处于同步代码中,如果想要 ‘456’ 在 ‘123’ 执行结束后再输出呢?
传统回调函数方式:
setTimeout( ()=>{
console.log('123');
fn();
},0 )
function fn(){
console.log('456')
}
8 // 123
9 // 456
使用 Promise 方式:
let promise = new Promise( (resolve,reject) =>{
setTimeout( ()=>{
console.log('123');
resolve('456');
} ,0)
} )
promise.then(function(data){
// resolve状态
console.log(data);
},function(error){
// reject状态
})
13 // 123
14 // 456
Promise 实例生成以后,可以用 then 方法分别指定 resolved 状态和 rejected 状态的回调函数。也就是说,状态由实例化时的参数(两个不同状态的回调函数)执行来决定的,根据不同的
状态来执行具体哪个函数。
注:resolve() 和 reject() 的阐述会传递到对应的回调函数的 data 或者 error,且 then 返回的是一个新的 Promise 实例,也就是或还可以继续 then
链式操作用法 从 表面行看,我们或许会觉得 Promise 只是能够简化传统的层层回调写法。然而,Promise的精髓是 ‘状态’,用维护状态,传递状态的方式来使得回调函数能够及时调用,它比传递 callback 函数要灵活、简单的多。下面为一个 Promise 的一般使用场景:
async1()
.then(function(data){
console.log(data);
return async2();
})
.then(function(data){
console.log(data);
return async3();
})
.then(function(data){
console.log(data);
}); function async1(){
let p = new Promise(function(resolve,reject){
// 异步操作
setTimeout(()=>{
console.log('异步任务1执行完成!');
resolve('数据1');
},1000);
});
return p;
};
function async2(){
let p = new Promise((resolve,reject)=>{
// 异步操作
setTimeout(()=>{
console.log('异步任务2执行完成!');
resolve('数据2');
},2000);
});
return p;
};
function async3(){
let p = new Promise((resolve,reject)=>{
// 异步操作
setTimeout(()=>{
console.log('异步任务3执行完成!');
resolve('数据3');
},3000);
});
return p;
}
// 1秒后...
// 异步任务1执行完成!
// 数据1
// 2秒后...
// 异步任务2执行完成!
// 数据2
// 3秒后...
// 异步任务3执行完成!
// 数据3
在 then 方法中,可以不用 return Promise实例对象,直接返回数据在后面的 then 中也能够接收到数据
reject用法 在前面的例子中只有 resolve(成功状态)的回调,实际应用中还会有失败状态,reject 就是把 Promise 的状态设置为 rejected ,这样我们就能够在 then 中捕捉到,然后执行相应的回调
let num = 10;
let p1 = function(){
return new Promise((resolve,reject)=>{
if(num <= 5){
resolve("<=5,走resolve");
console.log("resolve不能结束Promise");
}
else{
reject(">5,走reject");
console.log("reject不能结束Promise")
}
})
} p1()
.then(function(data){
console.log(data)
},function(error){
console.log(error)
})
// reject不能结束Promise
// >5,走reject
resolve 和 reject 永远在当前环境的最后面执行,所以后面的同步代码会先执行
如果 resolve 和 reject 之后还有代码需要执行,最好放在 then 里,然后在 resolve 和 reject 前面写上 return
Promise.prototype.catch() Promise.prototype.catch() 方法是 .then( null, rejection ) 的别名,用于指定发生错误的回调函数
p1()
.then(function(data){
console.log(data)
})
.catch(function(err){
console.log(err)
})
//reject不能结束Promise
//>5,走reject
Promise.all()
Promise.all() 方法用于将多个 Promise 实例包装成一个新的 Promise 实例
const p = Promise.all( [p1,p2,p3] );
p 的状态由 p1、p2、p3 决定,分为两种情况:
- 只有 p1、p2、p3 的状态都为 resolved 时,p 状态才会变成 resolved,此时 p1、p2、p3 的返回值组成一个数组传递给 p 的回调函数
- 只要 p1、p2、p3之中有一个状态为 rejected ,p 的状态就变成 rejected,此时第一个状态 为 rejected 的实例的返回值会传递给 p 的回调函数
由于p 是包含3个 Promise 实例的数组,只有这三个实例状态都为 resolved,或者其中有一个及以上的实例状态为 rejected 时,才会调用 Promise.all 方法后面的回调函数
如果作为参数的 Promise 实例自己定义了 catch 方法,那么它一旦被 rejected,并不会触发 Promise.all 的 catch 方法,如果没有实例参数定义自己的 catch,就会调用 Promise.all 的 catch 方法
Promise.race() Promise.race() 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.race( [p1,p2,p3] )
使用该方法时,只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就会跟着该实例变化,该实例的返回值传递给 p 的回调函数
Promise.resolve() 在实际应用中有时需要将一个对象转为 Promise 对象,Promise.resolve() 方法就能够实现,该实例的状态为 resolved
const promise = Promise.resolve( '123' ); // 等价于 Promise.resolve( '123' );
new Promise( resolve => resolve( '123' ) )
Promise.resolve 方法的参数类型有四种情况:
- 参数为一个 Promise 实例
- 如果参数就是 Promise 实例,那么 Promise.resolve 将不做任何修改,依然返回该实例
- 参数为一个 thenable 对象
- thenable 对象指具有 then 方法的对象,如:
let thenable = {
then: function( resolve,reject ){
resolve( 'aaa' );
}
} - Promise.resolve 方法会将这个对象转为 Promise 对象,然后立即执行 thenable 对象的 then 方法
let thenable = {
then: function(resolve, reject) {
resolve( 'aaa' );
}
}; let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // aaa
})上面代码中,thenable 对象的 then 方法执行后,对象 p1 的状态就变为 resolved,从而立即执行后面那个 then 方法指定的回调函数,输出 aaa
- thenable 对象指具有 then 方法的对象,如:
- 参数不是具有 then 方法的对象,或者根本不是一个对象
- 如果参数是一个原始值,或者不是一个具有 then 方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 resolved
const p = Promise.resolve('Hello'); p.then(function (s){
console.log(s)
});
// Hello上面代码生成一个新的 Promise 对象的实例 p,由于字符串 Hello 不属于异步操作( String 对象不具有 then 方法),返回 Promise 实例的状态生成就为 resolved,所以回调函数会立即执行,且 Promise.resolve 方法的参数会同时传给回调函数
- 如果参数是一个原始值,或者不是一个具有 then 方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 resolved
- 参数为空
- Promise.resolve 方法可以不带参数使用,此时直接返回一个 resolve 状态的 Promise 对象
- 如果希望得到一个 Promise 对象,比较方便的方法就是直接调用不带参数的 Promise.resolve 方法
const p = Promise.resolve(); p.then(function () {
// ...
}); 上面变量 p 就是一个 Promise 对象,注:立即 resolve 的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时
setTimeout(function () {
console.log('three');
}, 0); Promise.resolve().then(function () {
console.log('two');
}); console.log('one'); // one
// two
// three上面代码中,setTimeout(fn,0)在下一轮“事件循环”开始时执行,Promise。resolve() 在本轮执行,console.log('one')则是立即执行,因此最先输出
Promise.reject()
Promise.reject( reason ) 方法也会返回一个新的 Promise 实例,该实例的状态为 rejected
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了')) p.then(null, function (s) {
console.log(s)
});
// 出错了
上面代码生成一个 Promise 对象的实例 p,状态为rejected,回调函数会立即执行
注:Promise.reject() 方法的参数,会原封不动的作为 reject 的理由,变成后续方法的参数。这一点与 Promise.resolve 方法不一致
const thenable = {
then(resolve, reject) {
reject('出错了');
}
}; Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
上面代码中, Promise.reject 方法的参数为一个 thenable 对象,执行以后,后面 catch 方法的参数不是 reject 抛出的 ‘出错了’ 这个字符串,而是 thenable 对象
两个附加的方法
ES6 的 Promise 提供的 API 不是很多,有些有用的方法可以自己部署,下面介绍两个不在 ES6 中,但很有用的方法
done()
Promise 对象的回调链,不管以 then 方法或者 catch 方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(Promise内部的错误不会冒泡到全局)。因此,可以提供一个 done 方法,总是处于回调链的末端,保证抛出任何可能出现的错误被捕捉
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();
实例:
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (reason) {
// 抛出一个全局错误
setTimeout(() => { throw reason }, 0);
});
};
上面代码可见,done 方法的使用,可以像 then 方法那样用,提供 resolved 和 rejected 状态的回调函数,也可以不提供任何参数。总之,done 都能够捕捉到任何可能出现的错误,并向全局抛出
finally()
finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。它与 done 方法最大的区别是 finally 方法能够接收一个普通的回调函数作为参数,该函数不管怎样都必须执行
实例:服务器使用 Promise 处理请求,然后使用 finally 方法关掉服务器
server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
上面代码中,不管前面的 Promise 是resolved 状态还是 rejected 状态,都会执行回调函数callback
浅谈Promise原理与应用的更多相关文章
- 浅谈xss原理
近日,论坛上面XSS满天飞,各处都能够见到XSS的痕迹,前段时间论坛上面也出现了XSS的迹象.然后我等小菜不是太懂啊,怎么办?没办法仅仅有求助度娘跟谷歌这对情侣了. 能够说小菜也算懂了一些.不敢藏私, ...
- 浅谈 zookeeper 原理,安装和配置
当前云计算流行, 单一机器额的处理能力已经不能满足我们的需求,不得不采用大量的服务集群.服务集群对外提供服务的过程中,有很多的配置需要随时更新,服务间需要协调工作,那么这些信息如何推送到各个节点?并且 ...
- 浅谈Promise
学习过JavaScript的人都知道,JavaScript是单线程作业,这样会有一个很大的缺陷,所有的Ajax,浏览器事件等,都是通过异步去完成.所谓的同步和异步最大的区别无非就是在于同步会阻塞后续代 ...
- [Hadoop]浅谈MapReduce原理及执行流程
MapReduce MapReduce原理非常重要,hive与spark都是基于MR原理 MapReduce采用多进程,方便对每个任务资源控制和调配,但是进程消耗更多的启动时间,因此MR时效性不高.适 ...
- 浅谈AQS原理
一.AQS介绍 AQS,即AbstractQueuedSynchronizer, 抽象队列同步器,它是Java多线程模块用来构建锁和其他同步组件的基础框架.来看下同步组件对AQS的使用: AQS是一个 ...
- ConcurrentHashMap——浅谈实现原理及源码
本文整理自漫画:什么是ConcurrentHashMap? - 小灰的文章 - 知乎 .已获得作者授权. HashMap 在高并发下会出现链表环,从而导致程序出现死循环.高并发下避免HashMap 出 ...
- 浅谈HashMap原理,记录entrySet中的一些疑问
HashMap的底层的一些变量: transient Node<K,V>[] table; //存储数据的Node数组 transient Set<java.util.Map.Ent ...
- 浅谈systemd原理和应用
多不说,直接上代码(可谓配置): [Unit] Description=demo app After=network-is-online.target [Service] Type=Simple Ex ...
- 浅谈CAS(Compare and Swap) 原理
浅谈CAS原理java并发编程也研究了一段时间了,对CAS的原理总是不太理解,今天再研究了一下,记录一些自己的理解. 说到CAS,再java中的某些情况下,甚至jdk1.5以后的大多数情况,并发 ...
随机推荐
- Java动态修改运行环境
1.pom.xml直接添加一下配置 <profiles> <profile> <id>dev</id> <properties> <a ...
- set serveroutput on
使用set serveroutput on 命令设置环境变量serveroutput为打开状态,从而使得pl/sql程序能够在SQL*plus中输出结果 使用函数dbms_output.put_lin ...
- Redis查询_Tips
基础知识——介绍 Redis简介 REmote Dictionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统. Redis是一个完全 ...
- double,float,BigDecimal类型数值的操作
float四舍五入保留两位小数 /** * float四舍五入保留两位小数 * */ public static float formatDecimal(float n) { return (Math ...
- 动手生成 Delphi xe DBTreeview
tProductType表结构如下 object FDConnection1: TFDConnection Params.Strings = ( 'Database=ClothingT ...
- 【计算机视觉】MTCNN的windows-cpu配置
前言 MTCNN是级联卷积网络,原理基本上比较清晰,只是还缺少实战,看到一个CSDN上windows的实现过程,就拿来试试. 操作过程 参考here,某些步骤会添加博主遇到的问题的解释. 第一部分:c ...
- swift 第十四课 可视化view: @IBDesignable 、@IBInspectable
以前应objctiew-c 写项目的时候,就知道有这两个关键字,现在用swift了.用法稍作改变,基本用法还是一致的 虽然使用这个之后,有时候会报错的非常的莫名其妙----(其实还是自己技术不够牛…… ...
- swift 第二课 基础知识-2
setter 和getter 的使用--> 适合计算使用 struct Point { var x = 0.0, y=0.0 } struct Size { var width = 0.0, h ...
- Python数据结构与语法
字典:Python字典是另一种可变容器模型,且可存储任意类型对象,如字符串.数字.元组.字典等其他容器模型:值可以取任何数据类型,但键必须是不可变的,如字符串,数字或元组:遍历字典时遍历的是键:访问v ...
- 可执行jar包与依赖jar包
1.在IDEA的pom文件中有如下配置的,打包出来的是可执行jar包,可执行jar包不能作为依赖. <build> <plugins> <plugin> <g ...