【Javascript】我应该搞懂Promise了吧

一、 为什么我们需要Promise?

       在Promise出现之前,我们一般用定义回调函数来完成异步操作(这里补充一下,异步操作并不一定计算量大或者要等很长时间,只要我们不想为了等待某个异步操作而阻塞线程执行,在任何时候都可以使用)

function double(value) {
	setTimeout(() => setTimeout(console.log, 0, value * 2),1000);
double(3);
//6,大概1000毫秒后

       大部分情况下,回调嵌套都不是很多,但是某些特殊情况下,比如说异步返回值又以来另一个异步返回值,那么回调的情况会愈加复杂,代码就会非常繁琐,嵌套回调的代码维护起来十分困难,这种情况我们称之为——“回调地狱”。于是乎,Promise(期约)应运而生。

二、期约基础

       Promise,期约,是对尚不存在的结果的一个替身。可以理解为它保存着某个未来才会结束的事件的结果。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
       ECMAScript新增了引用类型Promise,所以Promise是一个对象,我们可以通过new操作符来实例化。
       期约是一个有状态的对象。有以下三种状态:

  • 待定(pending)
  • 兑现(fullfilled,也称解决,resolved)
  • 拒绝(reject)

       待定(pending)是期约的最初始状态。在待定状态下,期约可以落定 (settled)为兑现状态(fulfilled)状态——代表成功,或拒绝状态(reject)——代表失败。
       无论落定成哪种状态都是不可逆的。只要期约的状态由待定变为兑现/拒绝,期约的状态就不再改变。
       非常重要的是,期约的状态是私有的,不可以直接通过JavaScript检测到,另外,期约的状态也不能被外部的JavaScript代码修改。期约故意将异步行为封装起来,从而隔离外部的同步代码
       每个期约只要状态切换为兑现,就会有一个私有的内部值(value),每个期约只要状态切换为拒绝,就会有一个私有的内部理由(reason)

Promise的构造函数接收一个参数 :执行器函数,并且,构造器函数需要传入两个参数

  • resolve:异步操作执行成功后的回调函数,调用resolve()会把状态切换为fullfilled/resolved
  • reject:异步操作执行失败后的回调函数,调用reject()会把状态切换为rejected,同时抛出错误

三、 Promise基本用法

       光说不练假把式,让我们先来new实例化一个Promise对象康康。

let p = new Promise((resolve, reject) => {
    //做一些异步操作
    setTimeout(() => {
        console.log('执行完成');
        resolve('YES!!');
    }, 2000);
});

       注意!这段代码里面我们只是把一个新的Promise实例赋给了p,并没有调用它,但是这个Promise里的异步函数会立刻执行。所有我们不需要它立即执行的时候,可以把它包在一个函数中,在需要的时候去运行这个函数。

1. Promise.resolve()

       期约并不是一开始就必须处于待定状态,然后通过执行器函数才能转换为落定状态。Promise.resolve()是静态方法,可以通过调用此静态方法,实例化一个解决的期约。
       可以给此静态方法传入一个参数,把任何值都可以转换为一个期约

let p1 = new Promise((resolve, reject) => resolve() );
let p2 = Promise.resolve() 

setTimeout(console.log, 0, Promise.resolve() );
//Promise <resolved>: undefined

setTimeout(console.log, 0, Promise.resolve(3) );
//Promise<resolved>: 3

//会省略多余的参数
setTimeout(console.log, 0, Promise.resolve(4, 5, 6) );
//Promise<resolved>: 4

       对于这个静态方法而言,如果传入的参数本身是一个期约,那它的行为就类似于一个空包装。所以可以说Promise.resolve()是一个幂等方法。这个幂等性会保留传入期约的状态。

       这个静态方法可以包装任何非期约值,包括错误对象,并把它转换为解决的期约。

let p = Promise.resolve(new Error('foo'));

setTimeout(console.log, 0, p);
//Promise <resolved>: Error:foo

2. Promise.reject()

       与Promise.resolve()类似,Promise.reject()会实例化一个拒绝的期约,抛出一个异步错误(这个错误不可以通过try/catch捕获,只能通过拒绝处理程序捕获)

let p1 = new Promise((resolve, reject) => reject() );
let p2 = Promise.reject() 
//p1和p2实际上是一样的

       但是,Promise.reject()并没有幂等性。如果给它传入一个期约对象,它会把这个期约当成它返回拒绝期约的理由

let p = new Promise(() => {} );
setTimeout(console.log, 0, p);  //Promise <pending>
setTimeout(console.log, 0, Promise.resolve(p)); //Promise <pending>

setTimeout(console.log, 0, Promise.reject(Promise.resolve()));
//Promise<rejected>: Promise <resolved>

同步/异步执行的二元性

       try/catch之所以不可以捕获Promise.reject()抛出的错误,是因为拒绝期约的错误并没有抛到执行同步代码的线程里而是通过浏览器异步消息队列来处理的。这也暴露出**Promise期约的真正异步特性**:它们是同步对象,但也是异步执行模式的媒介

四、期约的实例方法

       期约的实例方法是链接外部同步代码与内部异步代码之间的桥梁

Promise.prototype.then()

       Promise.prototype.then()是为期约实例添加处理程序的主要方法。then()接收两个参数:onResolved处理程序和onRejected处理程序,分别在期约进入“兑现”和“拒绝”状态时执行。这俩操作一定是互斥的,因为期约状态只能转换为最终状态一次。
       两个处理程序参数都是可选的,并且传给then()的任何非函数类型的参数都会被静默忽略。如果想只提供onRejected参数,那就要在onResolved参数上传入undefined,避免在内存中创建多余的对象。
       Promise.prototype.then()方法返回一个新的期约实例。这个新期约实例基于onResolved处理程序的返回值构建,也就是说,返回值会通过Promise.resolve()包装来生成新期约,onRejected处理程序的返回值也会被Promise.resolve()包装,因为onRejected处理程序的任务就是捕获异步错误,只要它成功地捕获了这个错误,就完成了它的使命,所以应该返回一个解决期约。

function onReasolved(id) {
	setTimeout(console.log, 0, id, 'resolved');
}
function onRejected(id) {
	setTimeout(console.log, 0, id, 'rejected');
}	

let p1 = new Promise( (resolved, reject) => {
	setTimeout(resolve, 3000));
let p2 = new Promise( (resolved, reject) => {
	setTimeout(reject, 3000));
	
p2.then(null, () => onRejected('p2'));
//p2 rejected (3秒后)

Promise.prototype.catch()

       Promise.prototype.catch()方法用于给期约添加拒绝处理程序,它只接收一个参数:onRejected处理程序,它其实就是一个语法糖,相当于调用Promise.prototype.then(null, onRejected)。它返回的也是一个新的期约实例。

let p = Promise.reject();
let onRejected = function(e) {
	setTimeout(console.log, 0, 'rejected');
};

//下面两种方式是一样的~
p.then(null, onRejected); //rejected
p.catch(onRejected);   //rejected

Promise.prototype.finally()

       Promise.prototype.finally()方法用于给期约添加onFinally处理程序,不管期约转换为解决或者是拒绝,它都会执行,可以帮助onResolvedonRejected处理程序出现冗余代码,但是它也没办法知道期约的状态时解决还是拒绝,所以一般我们用它来添加清理代码。。它返回一个新的期约实例。

期约连锁

       上面每个期约实例的方法(then()、catch()、finally())都会返回一个新的期约对象,而这个新期约又有自己的实例方法,所以我们可以把期约逐个串联起来,形成连缀方法,构成所谓的“期约连锁”。

let p = new Promise((resolve, reject) => {
	console.log('first');
	resolve();
});

p.then( () => console.log('second'))
 .then( () => console.log('third'));
 .then( () => console.log('fourth'));

//first
//second
//third
//fourth

Promise.all()和Promise.race()

       Promise类提供两个将多个期约实例组合成一个期约的静态方法:Promise.all()Promise.race()

Promise.all()

       PromisePromise.all()静态方法创建的期约,会在一组期约全都解决之后,才会解决,它返回一个新期约。要是有一个期约在待定,合成的期约也待定,要是有一个期约是拒绝,合成的期约也会拒绝。

let Promise1 = new Promise(function(resolve, reject){})
let Promise2 = new Promise(function(resolve, reject){})
let Promise3 = new Promise(function(resolve, reject){})
 
let p = Promise.all([Promise1, Promise2, Promise3])
 
p.then(funciton(){
  // 三个都成功则成功  
}, function(){
  // 只要有失败,则失败 
})
const p1 = Promise.resolve('p1')

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2 延时一秒')
  }, 1000)
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p3 延时两秒')
  }, 2000)
})

const p4 = Promise.reject('p4 rejected')

const p5 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p5 rejected 延时1.5秒')
  }, 1500)
})

// 所有Promise实例都成功
Promise.all([p1, p2, p3])
  .then(res => {
    console.log(res)
  })
  .catch(err => console.log(err)) // 2秒后打印 [ 'p1', 'p2 延时一秒', 'p3 延时两秒' ]
  
// 一个Promise实例失败
Promise.all([p1, p2, p4])
  .then(res => {
    console.log(res)
  })
  .catch(err => console.log(err)) // p4 rejected
  
// 一个延时失败的Promise
 Promise.all([p1, p2, p5])
  .then(res => {
    console.log(res)
  })
  .catch(err => console.log(err)) // 1.5秒后打印 p5 rejected
  
// 两个Promise实例失败
Promise.all([p1, p4, p5])
  .then(res => {
    console.log(res)
  })
  .catch(err => console.log(err)) // p4 rejected



Promise.race()

       Promise.race()静态方法创建的期约,是一组集合中最先解决或拒绝的期约的镜像。race,竞赛,谁先完成,谁先落定期约状态,我Promise.race()就会包装其解决值或拒绝理由,并返回一个新的包装期约。

 //请求某个图片资源
    function requestImg(){
        var p = new Promise((resolve, reject) => {
            var img = new Image();
            img.onload = function(){
                resolve(img);
            }
            img.src = '图片的路径';
        });
        return p;
    }
    //延时函数,用于给请求计时
    function timeout(){
        var p = new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('图片请求超时');
            }, 5000);
        });
        return p;
    }
    Promise.race([requestImg(), timeout()]).then((data) =>{
        console.log(data);
    }).catch((err) => {
        console.log(err);
    });
const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2 延时一秒')
  }, 1000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p3 延时两秒')
  }, 2000)
})

const p4 = Promise.reject('p4 rejected')

const p5 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p5 rejected 延时1秒')
  }, 1500)
})

// p1无延时,p2延时1s,p3延时2s
Promise.race([p1, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p1

// p4无延时reject
Promise.race([p4, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // p4 rejected
  
// p5 延时1.5秒reject,p2延时1s
Promise.race([p5, p2, p3])
  .then(res => console.log(res))
  .catch(err => console.log(err)) // 1s后打印: p2 延时一秒

完结撒花~~~~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值