JS中Promise简单了解

什么是promise呢?就是异步编程的一种解决方案。简单来看,可以把它看作一个容器,里面存放着某个未来才会结束的事件的结果。从语法上来说,它是一个对象,可以从它来获取异步操作的消息

这样可能好理解一些

比如去KFC买了一个汉堡,通过下订单并付款,此时就是发送了一个对某个值(就是那个汉堡)的请求。这时,服务员会给你一个排号,这个排号就是一个promise,保证你能拿到那个值(那个汉堡)。拿到那个排号后,你就可以做其他事情了,不必一直等到服务员给你汉堡。在你做其他事情时,服务员喊你的排号让你过去,此时可能会给你一个汉堡,然后你把排号给他,这是成功的结果,就是你拿到了你所请求的值。另一种可能是告诉你汉堡没了,让你换一个,然后你把排号给他,这是失败的结果,你没有拿到你所需要的值。但是,不管如何,只要你有这个排号(promise),那么就一定会有一种结果,成功或失败。而且,这种结果一旦产生就不会改变

了解了这点,应该能理解promise对象接受一个函数作参数,这个函数又接受两个函数作为参数

let promise=new Promise(function(resolve,reject){  //...code  if(/*异步成功*/){    resolve(value);  }else{    reject(error);  }});

再接着上面的故事,当你拿到汉堡后,你就可以离开KFC,然后去做其他事情。或者你没有拿到汉堡,然后你又重新选了另一种食物。不管如何,在你得到其中一种结果后,你都会有相应的对策。这种处理接收到的值(汉堡)的应对方案就是then()方法,这个方法也是有两个函数作为参数,分别对应拿到汉堡和没拿到汉堡的情况

promise.then((value) => {  //success 拿到汉堡},(error) => {  //failure 没拿到汉堡});

这样子应该很好理解这个promise

那么先来简单使用一下

let promise=new Promise((resolve,reject) => {  console.log("Promise");  resolve();});promise.then(() => {  console.log("Resolved");})console.log("Hi!");//Promise//Hi!//Resolved

Promise新建后会立即执行,所以首先输出的是Promise。然后then方法指定的回调函数会在所有的同步任务执行完成之后再执行,所以先输出Hi!,最后再输出Resolved

一定要注意,promise对象中的函数会立即执行,而then()方法中的回调函数会异步执行

 

对于这个promise对象。其实,并不一定要是通过new出来的。就像那个排号,并不一定非得是拿个排号才能取到汉堡,也可能是拿一张汉堡兑换券去取汉堡,也就是说只要具有能换取汉堡的特征都能被当成是一个promise。那么promise对象也是如此,如何区分呢,就是可以认为只要有then()方法的函数或对象都可以看成是一个promise

比如下面这些对象

var obj={  then(){}}var v=Object.create(obj);Object.prototype.then=function(){};var v1={ hello:"hello world"};

obj,v,v1都会被认为是一个promise对象。所以当对一个对象或函数定义方法时要注意

那么这是如何检测出来的呢?大致可以类似于下面这样

if(p !== null &&(typeof p ==="object" ||typeof p === "function") &&typeof p.then === "function"){  //可以把P当成promise来使用}else{  //不可以把P当成promise来使用}

但是呢,这里还是有一个小小的区分,通过new出来的叫promise实例,而通过添加then()方法而被认为是promise对象的叫thenable对象

 

至于如何使用promise对象,就以api的形式来细说

promise.resolve()方法

Promise.resolve()//等价于new Promise((resolve,reject) => resolve());

接收一个参数,返回一个安全的结果为Resolved的promise对象

//参数为Promise对象,则直接返回这个对象,不做任何修改let p=new Promise((resolve,reject) => {  resolve("成功了");});Promise.resolve(p).then((value) => {  console.log(value);});//成功了//参数为thenable对象,则会将这个对象转换为Promise对象,立即执行该对象//中的then()方法let thenable={  then(resolve,reject){    resolve(42);  }}let p=Promise.resolve(thenable);p.then((value)=>{  console.log(value);});console.log("now");//now //42//参数不具有then方法或者不是对象,则返回一个由该值填充的promise对象,//结果为ResolvedsetTimeout(()=>{  console.log("later");},0);let p=Promise.resolve(5);p.then((value)=>{  console.log(value);});console.log("now");//now //5//later//当不带有参数时,直接返回一个Resolved结果的promise对象(所以,//如果希望得到一个promise对象,可直接调用该方法)setTimeout(()=>{  console.log("three");},0);Promise.resolve().then(()=>{  console.log("two");});console.log("one");//one//two//three

 

promise.reject()方法

Promise.reject()//等价于new Promise((resolve,reject) => reject());

接收一个参数,返回一个结果为Rejected的promise对象

//参数为promise对象,直接返回这个对象let p=Promise.reject("出错了");p.then(null,(value) =>{  console.log(value);});//出错了//参数为thenable对象,注意这里的返回值是thenable对象,而不是//执行then方法let thenable={  then(resolve,reject){    reject("出错了");  }}Promise.reject(thenable).then(null,(value) =>{  console.log(value);});//{then: ƒ}//参数不具有then方法或者不是对象,则返回一个由该值填充的promise对象,//结果为RejectPromise.reject(4).then(null,(value) =>{  console.log(value);});//4

可以看出,resolve和reject方法其实没多大区别,只是当处理参数为thenable对象时方式不同,这点要注意

 

promise.then()方法

这个方法接收两个参数,第一个参数是结果为Resolved的回调函数,第二个是结果为Rejected的回调函数。返回一个新的promise对象。不管从这个then方法返回的是什么值,都会被自动链接到下一个then方法并作为其参数。对于第二个回调函数,如果没有写,则会有一个默认的拒绝处理函数顶替

let p=Promise.reject("出错了");p.then(function resolve(){  console.log("成功了");}).then(null,(value) => {  console.log(value);});//出错了

默认的拒绝处理函数只是会把错误重新抛出,并不会进行处理。本质上来说,这个错误会一直沿着这个promise链传播下去,直到遇到一个显示定义的拒绝处理函数来处理这个错误

 那么现在写一个简单的promise链

function delay(time){  return new Promise((resolve,reject) =>{    setTimeout(resolve,time);  });}delay(100).then(function step2(){  console.log("step 2 after 100ms");  return delay(200);}).then(function step3(){  console.log("step 3 after 100ms");  return delay(300);}); //step 2 after 100ms//step 3 after 100ms

 

promise.catch()方法

p.catch((error) => { console.log(error) });//等同于p.then(null,(error) => { console.log(error) });

接收一个参数,同时返回新的promise对象

专门用来处理异步操作过程中抛出的错误

let p=new Promise((resolve,reject) => {  reject("失败了");//也可以写成throw new Error()})p.then(() => {  console.log("one");}).catch((value) => {  console.log(value);  return value;}).then((value) => {  console.log(value,"two","成功了");})//失败了//失败了 two 成功了

这种错误是能被catch所捕获的,这是因为这个错误就代表了promise的结果。但是,如果在有了promise结果之后还抛出一个错误,那么这个错误就不会被捕获,也不会被传递到外层代码中,即不会打断整个程序的运行。至于为什么不能被捕获,是因为promise已经有了结果,不可更改,具有不可变性,如果此时再去捕获,则说明promise结果被更改,违反了该规则

let p=new Promise((resolve,reject) => {  resolve("成了");  throw new Error("失败了");}).then((value) => {  console.log(value);}).catch(() => {  console.log("我捕获到了");})setTimeout(() => {  console.log("我是外层代码运行结果");},0);//成了//我是外层代码运行结果

这就是在有了promise结果之后再抛出错误,但是没有被catch所捕获,同时也没有传递到外层代码中,只是留在了promise链中

还有一种错误也是无法被catch所捕获的

let p=new Promise((resolve,reject) => {  setTimeout(function(){    throw new Error("test")  },0);})p.then((value) => {  console.log("我成了");}).catch((value) => {  console.log(value);})setTimeout(() => {  console.log("我是外层代码")},0);console.log("a");//a//Uncaught Error: test//at 18.html:357//我是外层代码

这个promise是运行在本轮的事件循环中,setTimeout是运行在下一轮的事件循环之前开始。而promise指定在下一轮事件循环再抛出错误,则无法被本轮的事件循环中catch所捕获,所以在本轮的promise链中不会执行任何代码,当然也不会打断程序的运行。但是这里的错误会传递到最外层,这点与之前不同。为什么是传递到最外层,因为不是在本轮事件循环中报错

所以,到这里可以清楚一点,就是只要是在promise链中抛出错误,都不会打断程序的运行。而所抛出的错误会一直传递下去,直到遇见显示拒绝处理函数来处理(catch或then方法中的第二个参数)。如果promise链中没有报错,则这个catch会被忽略

 

promise.all()方法

接收一个数组参数(也不一定是数组,但是必须具备Iterator接口,这个下下篇写),通常由Promise实例组成(也不一定,也可以是promise,thenable对象和立即值,如果是这些,则都通过promise.resolve方法进行包装成为一个promise对象,如果是一个空数组则会立即完成)。从这个promise.all()返回的主promise在且仅在所有的promise成员都完成之后才完成。如果这些promise中有任何一个结果为Rejected的,则这个主Promise就会立即被拒绝,并丢弃所有来自其他promise的全部结果

//所有promise成员都完成了let p=Promise.resolve("成了");let thenable={  then(resolve){    resolve("成了");  }}Promise.all([p,thenable,20]).then((value) => {  console.log(value);}).catch((err) => {  console.log(err)})//["成了", "成了", 20]//有一个promise是Rejectedlet p=Promise.resolve("成了");let s=Promise.reject("没了");let thenable={  then(resolve){    resolve("成了");  }}Promise.all([s,p,thenable,20]).then((value) => {  console.log(value);}).catch((err) => {  console.log(err)})//没了

这些都还是很好理解的。但是有一个问题,如果传入数组的promise中已经定义了catch方法,而在promise.all()中也定义了catch方法,那么会先调用谁的呢?

let p=new Promise((resolve,reject) =>{  resolve("hello");}).then(result => result).catch(e => e);let q=new Promise((resolve,reject) => {  throw new Error("出错了");}).then(result => result).catch(e => e);Promise.all([q,p]).then(result => console.log(result)).catch(err => console.log(err));//[Error: 出错了 "hello"]

上面这段代码应该能看懂吧。p是结果为Resolved的promise对象,q首先会是结果为Rejected,然后调用自身定义的catch方法,返回一个新的结果为Resolved的promise对象并赋值给q。所以在all()中的promise对象都是结果为Resolved的,直接调用all()下面的then方法,而忽略catch方法

还有一段和这个很相似的代码

let p=Promise.resolve("hello world");p.then(result => result).catch( err => err)let q=Promise.reject("出错了");q.then(result => result).catch( err => err)Promise.all([p,q]).then((result) => {  console.log(result);}).catch( (err) => {  console.log(err);})//出错了

这段代码废了我好长一段时间,还是没能搞懂为什么和书上写的不一样,和MDN文档的也不一样。后来才发现是我写错了,不仔细看还真的不容易发现。谨记谨记!!!

 

promise.race()方法

该方法与all类似,接收一个数组参数,返回一个新的promise对象。但是,当数组中的promsie成员中任何一个最先获得最终结果(成功或失败),则race()方法返回这个promise对象

let p=new Promise((resolve,reject) =>{  setTimeout(() => {    resolve("我先执行啦");  },1000)})function timeOut(){  return new Promise((resolve,reject) => {    setTimeout(() => {      reject("你超时啦,不能再执行了")    },100)  })}Promise.race([p,timeOut()]).then((value) => {  console.log(value);}).catch((err) => {  console.log(err);})//你超时啦,不能再执行了

这个例子很简单,p是延迟1m执行resolve函数,返回一个结果为Resolved的promise对象,而timeOut函数则是在100ms执行reject函数,返回一个结果为Rejected的promise对象。谁先执行,race方法就返回谁的对象。很显然是timeOut函数先执行,则会抛出错误,由race定义的catch捕获错误

 

还有一种比较常遇到的情况,就是不管函数f是不是同步还是异步操作,就想用Preomise来操作这个函数。这样就可以使用then方法来指定下一步的流程,或者用catch来捕获错误。但是,如果只是直接在then方法中调用函数f则会有一些小问题产生,比如,这个函数f是同步函数,当在then方法中时则变为了异步执行,这可能不是你想要的结果

let f= () => console.log("now");Promise.resolve().then(f);console.log("later");//later//now

那么,有没有解决方案呢?让同步函数任是同步执行,异步函数则异步执行有,且有两种(看看就行,不懂没关系,我也不懂)

第一种

使用async函数

let f= () => console.log("我是同步函数");let p= setTimeout(() => console.log("我是异步函数"), 10);(async () => f())();(async () => p)();console.log("wo");//我是同步函数//wo//我是异步函数

第二种

使用new Promise()

let f= () => console.log("我是同步函数");let p= setTimeout(() => console.log("我是异步函数"), 10);(() => new Promise(resolve => resolve(f())))();( () => new Promise(resolve => resolve(p)))();console.log("wo");//我是同步函数//wo//我是异步函数

 

到这里就差不多介绍完了Promise函数,最少能基本使用了。还有很多东西没写出来,有些知识看了书也不会=,=

我觉得这个Promise最关键的地方就是每个方法都会返回一个新的Promise对象,以此不断链接下去。然后通过两种回调方法来处理成功或失败的情况,最后还有一个catch来捕获抛出的错误

这个返回新的Promise对象很重要。如果只是单纯的使用回调函数,那么就无法判断这个回调函数是否被调用了,或者调用了几次,又或是提前调用和调用过晚,这些情况都无从知晓,因为没有一个凭证来证明。但是Promise的出现就比较完美的解决了这些可能发生的问题。比如判断是否被调用,可以用promise.race()来判断,在一定时间内未执行则抛出一个自定义的错误来判定该函数未执行。调用几次的问题也不会发生,在promise链中每个then只会执行一次即返回一个新的promise对象。然后调用过早或过晚也是不太可能发生的,因为then方法是异步执行的,不会在同步函数之前被执行,但是下一个异步时机点(我的理解是在同步执行完后)上被依次立即执行

 

 

PS:

js是单线程的

事件循环:将所有代码按照块来区分,每次执行一个代码块,这就是事件,每执行完一个代码块,立即执行下一个块,这是循环

任务队列:每个块中所要执行的代码按照任务来区分,不同代码执行不同任务,总是先执行同步任务。队列就是按照顺序来挨个执行

这两个有很明显的区别,可以这么看:在游乐园玩一个游戏,事件循环就是在玩完一个游戏后重新排在最后面来重新玩,而任务队列就是玩完之后可以插队接着玩,能一直玩下去

为什么要说这些,因为promsie的异步特性是基于任务的,就是它能一直插队来执行

 

不理解没关系,我也不是很懂

下一篇,生成器,迭代器,有点难懂啊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值