Promise系列1—— 基本用法

681 阅读5分钟

Promise 基本用法

promise 解决了什么问题

如果别人问我 Promise 解决了什么问题?我会毫无疑问的回答解决了回调地狱的问题。所以要想弄清楚 Promise 到底是个什么玩意儿,还要弄清楚以下几个问题:

  1. 什么是回调地狱
  2. 为什么会有回调地狱
  3. promise 是如何解决回调地狱问题的

在实际的开发过程中,我们通常会遇到一种情况:当某个值达到一种状态的时候,立刻去执行另外一个任务,这时候就用到了回调函数,如下

var dynamicFunc = function(cb) {
	setTimeout(function() {
		cb(1,2);
	}, 1000);
}
dynamicFunc(function(a,b,cb) {
	const sum = a + b
  cb(sum)
});

👆上面的代码展示了,当我调用 dynamicFunc 函数的时候,想要一秒后计算一下 1 + 2 的值 ,这时候就用到了回调函数,在回调函数里面,我想要值计算完用到这个值,去执行另外的一个逻辑,就会继续用到回调函数。所以 当函数里面值状态改变需要执行另外的逻辑就要使用回调函数。 这样我门的代码就会横向变宽,形成了回调地狱。

由于 JavaScript 是一门单线程的语言,所以在promise 出现之前,我门解决异步的场景就需要用到回调函数,如下

$.ajax({
	url:'...',
  ...
  success:function(response){
  	....
  }
})

👆上面的代码示例是一个ajax 请求,发送请求后,⼀段时间服务端响应之后我们才能拿到结果。如果我们希望在异步结束之后执⾏某个操作,就只能通过回调函数这样的⽅式进⾏操作。 这就是一个典型的回调地狱。

在 ES6 中 js 产⽣了⼀个名为 promise 的新规范,使用另外的一种方式解决了回调地狱问题。ajax 请求会有两个 successerror。在 promise 里面也有两个函数 resolvereject 分别表示已完成已拒绝,表达的状态和 ajax 类似但是调用方式从 横向 转为 **纵向;**如下

const promise = () =>{
	return new Promise((resolve,reject)=>{
  	$.ajax({
        url:'...',
        ...
        success:function(response){
          resolve(response)
        }
      })
  })
}

promise().then(response=>{console.log(response)}) // Ajax 请求结果

promise 介绍

Promise 是 ES6 新增的一个引用类型,可以通过 new 操作符来实例化,在创建promise 实例的时候需要传入一个函数作为参数。基础语法如下

const p = new Promise(()=>{});
console.log(p) // Promise <pending>

在控制台输出 promise 实例,会输出一个处于 **待定(pending) **状态的 promise。根据输出可以看出 promise 是一个有状态的对象,promise 的状态只能处于以下三种的一种:

  • pending 待定
  • fulfilled  解决
  • rejected 拒绝

pending 是 promise 的最初状态,当处于 pending 状态的时候,根据相关逻辑状态可以改变(书上称作为落定settled)为 fulfilled 或者 rejected 状态。但是不管 promise 处于那种状态,都是不可逆的,并且如果promise 处于 fulfilled 或者 rejected 状态都是不可改变的。

实例化 promise 传入的函数通常称作 **执行器(executor) **函数。函数里面有两个函数作为参数 resolve 和 **reject。**通过调用这两个函数来改变 promise 的状态 resolve 会把状态改变为 fulfilled。reject 会把状态改变为 rejected。

const pPending = new Promise(()=>{});
console.log(pPending) // Promise <pending>

const pFulfilled = new Promise((resolve,reject)=>resolve());
console.log(pFulfilled) // Promise <fulfilled>

const pRejected = new Promise((resolve,reject)=>reject());
console.log(pRejected) // Promise <rejected>

Promise 实例创建完成后,可以通过 then 方法获得到 fulfilled 和 rejected 状态。基本语法如下

const promise = (val) =>{
	return new Promise((resolve,reject)=>{
		  if(val){
      	resolve(val)
      }else{
        reject(val)
      }
  })
}

promise(true)
	.then(()=>{
	// fulfilled	
},()=>{
	// rejected
})

可以看出 then 方法也接受两个函数,分别对应resolve 和 reject 的处理结果。第二个函数可选,如果不写第二个函数可以通过 catch 方法 处理 reject 结果,如下

promise(true)
	.then(()=>{
	// fulfilled	
})
  .catch(()=>{
	// rejected
})

promise 常用方法

  • Promise.resolve()

会返回一个 Promise 实例,并且把状态改为 fulfilled。如下

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

在使用这种方式的时候,传参传值分为四种情况

  1. Promise 实例

如果参数是一个 promise 实例,那么会把这个实例返回,可以说和没使用这个方法是一样的哦。

  1. 含有then方法的对象

如果参数是一个具有 then 方法的对象,如果then方法是一个 thenable 函数则调用 promise.resolve() 方法会返回 对象里面then方法返回值;如果then方法是一个普通函数,不会有任何响应。如下

const thenObj = {
	then:()=>{
  	return "Lius"
  }
}
const p1 = Promise.resolve(thenObj);
p1.then(res=>{console.log(">>>>",res)}); // 不输出

const thenable = {
	then:(resolve,reject)=>{
  	resolve("Lius")
  }
}
const p2 = Promise.resolve(thenable);
p2.then(res=>{console.log(res)}); // Lius

  1. 不含有 then 方法的对象,或者不是一个对象

如果传入的是一个普通对象,或者不是一个对象,会把原值返回;

const obj = {
	name:"Lius"
}
const p1 = Promise.resolve(obj);
const p2 = Promise.resolve("Lius");

p1.then(res=>{console.log(res)}); // {name: "Lius"}
p2.then(res=>{console.log(res)}); // Lius
  1. 不传参

如果不传参数,会返回一个状态 fulfilled 的 promise实例;

const p1 = Promise.resolve();
console.log(p1); // Promise <fulfilled>
  • Promise.reject()

返回一个状态为 rejected 的 promise 实例,调用 Promise.reject()方法,会把传参全部当做reject的原因返回,可以在catch方法里面捕捉

  • promise.all()

将多个promise 实例,封装成一个promise实例,基本语法如下

const p = Promise.all([p1,p2,p3])

Promise.all() 方法接收一个数组作为参数,需要注意的是数组元素都需要是promise实例,如果元素不是promise实例,会先调用promise.resolve方法转化为一个promise实例在进行处理。

此时 p 的状态,由 p1,p2,p3 同时决定,(1) 如果 p1, p2, p3 三个状态都为 fulfilled 那么 p 的状态就是 fulfilled;(2) 如果 p1, p2, p3 其中有一个状态是 rejected 那么 p 的状态就是 rejected。

在then 方法会按照传入参数的顺序,返回一个同样结果顺序的数组

const p1 = Promise.resolve("Lius")
const p2 = Promise.resolve("Lius1")
const p3 = Promise.resolve("Lius2")

Promise.all([p1,p2,p3])
	.then(res=>{
	const [p1Res,p2Res,p3Res] = res;
  console.log(p1Res,p2Res,p3Res); // Lius, Lius1, Lius2
})
  • promise.race()

Promise.race() 和 Promise.all() 的区别就在于 状态的改变,

p 的状态改变,只会受到p1, p2, p3, 其中的一个状态的影响,至于受到谁的影响,就要看他们谁先返回状态,率先返回的状态就是 p 的状态。

const p1 = Promise.resolve("Lius");
const p2 = () => {
	return new Promise((resolve,reject)=>{
  	setTimeout(reject("ERROR"),5000)
  })
}

const p = Promise.race([p1,p2])

p.then(res=>{
	console.log("SUCCESS",res) // SUCCESS, Lius
})
.catch(err=>{
	console.log("ERR.", err)
})

如何链式调用

需要注意的是 Promise.all() 和 Promise.race() 都是异步调用,在特殊的业务场景,我们需要链式调用,把 p1 返回的结果,作为参数传入 p1。常用的方法有两种,直接贴代码把

function promiseCreator(val, time) {
  return new Promise(resolve => {
    setTimeout(resolve(val), time);
  })
}

function promiseCreator2(val, time) {
  return new Promise(resolve => {
    setTimeout(resolve(val), time);
  })
}

const promiseCreatorList = [
  promiseCreator,
  promiseCreator2,
];

// 第一种 使用 reduce
const promiseChain = promiseList =>
  promiseList
    .reduce((acc, cur, index) =>
      acc
        .then(x => cur(`这是第${index}个promis;上一个值为=>${x}`, (index + 1) * 1000)),
      Promise.resolve(0)
    )
promiseChain(promiseCreatorList).then(res => console.log(res))

// 第二种,使用 for of
const promiseChain2 = promiseList => {
  let result = Promise.resolve(0)
  for (const [index, promise] of promiseList.entries()) {
    result = result.then(x => promise(`这是第${index}个promis;上一个值为=>${x}`, (index + 1) * 1000))
  }
  return result
}

promiseChain2(promiseCreatorList).then(res => console.log(res))

本文解释了 promise 解决了什么问题,promise 的如何使用,和在实际开发过程中常用的promise静态方法。下一篇将要介绍 promise 规范和自己实现一个 promise