JS ---Promise入门篇

333 阅读6分钟

这篇文章适合要接触Promise的朋友们阅读,里面写的是Promise中的一些基础知识,并没有涉及到太难理解的。

一、 初识Promise

1.1 什么是Promise

Promise:ES6中新提供的一个内置类(提到类可以想到new、原型、原型链、实例...),基于promise可以有效的管理JS中的异步编程,解决传统异步编程+回调函数导致的‘回调地狱’问题。
=> 我们把基于promise进行异步管控的模式叫做‘promise设计模式’

1.2 学习Promise需要掌握的内容

从函数有三个角色入手:普通函数、构造函数(类)、普通对象

【普通对象 => 静态的私有属性方法】

  • promise.all()
  • promise.race()
  • promise.resolve()
  • promise.reject()

【类 => 原型链上的公共属性和方法】

  • promise.prototype.then()
  • promise.prototype.catch()
  • promise.prototype.finally() [一般不用]

【new创建实例】

二、创建Promise实例

2.1 创建Promise实例的语法

语法: let 实例 = new Promise([executor])

说明:

  • 必须传一个函数,否则会报错:Uncaught TypeError: Promise resolver undefined is not a function
  • [executor]是一个函数,我们一般在函数中管控我们的异步编程代码
  • new Promise的时候就会把executor立即执行
  • 并且给executor函数传递两个实参(两个实参也都是函数):resolve/reject
let p1 = new Promise();  //必须传一个函数 Uncaught TypeError: Promise resolver undefined is not a function

let p2 = new Promise((resolve,reject)=>{
  //异步编程代码
});

2.2 Promise的两个值

Promise的实例拥有[[PromiseStatus]]/[[PromiseValue]]

[[PromiseStatus]]是promise状态(要么是成功态要么是失败态)

  • 准备状态pendingnew Promise的时候默认状态就是pending
  • 成功状态fulfilled/resolved:一般在异步操作成功后,我们通过执行resolved函数,可以把promise的状态改为resolved
  • 失败状态rejected:一般在异步操作失败后,我们通过执行reject函数,可以把promise的状态改为rejected

pending => resolved / rejected 只要状态一旦更改,则不可以再改变

[[PromiseValue]]是promise的值

  • 不论执行resolve/reject哪个函数,都可以传递值,传递的值最后赋值给[[PromiseValue]]

例如: 我们创建一个Promise实例p1,创建实例的时候我们在函数中把他的resolved函数执行,也就是把它变为成功态

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('ok');
        console.log(p1)   //=>'resolve' 'ok'
    }, 1000)
});
console.log(p1);  //=>'padding'   undefined
p1实例输出说明
p1实例输出说明

2.3 总结:创建实例时做的事情

我们在创建一个Promise的实例,需要给Promise传递一个函数,这个函数会立即执行,并且浏览器给它自带两个参数(两个参数也都是函数),这两个参数会改变Promise的状态和值

三、Promise原型链上的公共方法---then

3.1 then是干什么的

我们在创建实例的时候可以修改Promise的状态,目的就是为了控制then中的两个方法,哪一个去执行。
then方法:实例.then([状态成功时执行的],[状态失败时执行的])

  • result / reason 接收的是[[PromiseValue]]的信息(在executor函数中,基于resolve/reject执行传递的值,就是给promise-value传递的值,并且只能传递一个值,传递第二个实参没用)
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (Math.random() < 0.5) {
      reject('NO');
    } else {
      resolve('OK');
    }
  }, 1000);
});
p1.then(result => {
  console.log(`成功:${result}`); 
}, reason => {
  console.log(`失败:${reason}`); //当上面的随机是小于0.5的时候执行reject,也就是把状态改为失败态,从而执行then中的reason这个方法
});

3.2 then方法在什么时候执行?

1. 创建实例时执行的是异步代码:

  • 异步请求放置在EXECUTOR中,请求成功或者失败后做啥事情都写在THEN

2. 创建实例时执行的不是异步代码:

  • EXECUTOR函数中理论上是管控异步编程代码的,但是在开发中,你可以自己随意处理;但是不论怎么处理,THEN中的方法,只会在PROMISE状态变为成功或者失败的状态下才会执行;
  • EXECUTOR函数中执行RESOLVE或者REJECT,并不一定会立即通知THEN中的方法执行;如果在这两个函数执行之前,已经基于THEN把成功或者失败的方法放置好了,则立即通知执行;如果还没有执行过THEN方法,则需要等到THEN执行后,方法放置好,再通知成功或者失败的方法执行!
new Promise((resolve, reject) => {
  // 异步请求放置在EXECUTOR中,请求成功或者失败后做啥事情都写在THEN中
  $.ajax({
    url: '/api/info',
    method: 'get',
    success: result => {
      resolve(result);
    },
    error: reject
  });
}).then(result => {  
}, reason => {
});
new Promise((resolve, reject) => {
  reject(100);
}).then(result => {
  console.log(`成功:${result}`);
}, reason => {
  console.log(`失败:${reason}`);
});

3.3 then方法的返回值

每一次执行.then都会返回一个新的Promise实例(初始状态:pending初值:undefined)。这样可以继续.then下去,这就是Promise中的then链机制

下面的例子说明:
p2这个实例的成功或者失败状态,是由p1.then这堆代码决定的

  • 第一种情况:只要p1.then中不论哪个方法执行,只要不报错,新的p2实例的状态都会变为成功态,而方法返回的结果就是p2实例的promise-value值(也就是上一个then执行的返回结果,会传递给下一个then中的方法);同理,两个方法中,不管哪一个执行报错,P2一定是失败态
  • 第二种情况:如果p1.then中的某个方法执行,返回的是新的Promise实例,则会等待这个Promise的执行结果,作为p2的执行状态
let p1 = new Promise((resolve, reject) => {
   setTimeout(() => {
       if (Math.random() < 0.5) {
          reject('NO');
       } else {
          resolve('OK');
       }
    }, 1000);
});


let p2 = p1.then(result=>{  
},reason=>{  
});
console.log(p2);

let p3 = p2.then(result => {
  console.log(result); //=>'OK@@'
  // return Promise.resolve(100); //=>P3的状态变为成功,值是100
  return Promise.reject(0); //=>P3的状态变为失败,值是0
}, reason => {  
});

p3.then(result => {
  console.log('成功' + result);
}, reason => {
  console.log('失败' + reason);
});

3.4 then链的理解

下面代码的输出结果:把这段代码理解会then就掌握差不多了(链有点长😂,不着急,下面搭配了注释😊)

new Promise(resolve => {
  setTimeout(() => {
    resolve(10); //1000MS后 第一个PROMISE实例状态是成功  VALUE:10
  }, 1000);
}).then(result => {
  console.log(`成功:${result}`); //=>'成功:10'
  return result * n; //报错:ReferenceError: n is not defined  也就是让此次THEN返回的PROMISE实例变为失败态,VALUE:失败的原因
}, reason => {
  console.log(`失败:${reason}`);
  return reason * 10;
}).then(result => {
  console.log(`成功:${result}`);
  return result * 20;
}, reason => {
  console.log(`失败:${reason}`); //=>'失败: ReferenceError: n is not defined'
  return reason * 10; //NaN  代码执行没有报错,让当前THEN返回的实例状态:成功  VALUE:NAN
}).then(result => {
  console.log(`成功:${result}`); //=>'成功:NaN'
  return result * 20; //NaN  代码执行没有报错,让当前THEN返回的实例状态:成功  VALUE:NAN
}, reason => {
  console.log(`失败:${reason}`);
  return reason * 10;
}).then(result => {
  console.log(`成功:${result}`); //=>'成功:NaN'
  return Promise.reject(result * 20); //返回的新的失败的PROMISE实例影响了本次THEN返回PROMISE实例的状态和结果(和RETURN的保持一致)
}, reason => {
  console.log(`失败:${reason}`);
  return Promise.resolve(reason * 10);
}).then(result => {
  console.log(`成功:${result}`);
}, reason => {
  console.log(`失败:${reason}`); //=>'失败:NaN'
});

3.5 then中只写一个方法的理解

THEN中只设置一个方法,成功或者失败执行的方法没有设置,会顺延到下一个THEN中查找(依然是按照当前的状态查找,例如:失败的状态,第一个THEN没有设置失败的方法,会找第二个THEN中设置的失败的方法...)

比如么有设置 reason,那么需要执行它的时候默认做的是什么事情呢?
=> { 不写的情况下,PROMISE内部默认补充了一下(实现的效果:继续找下一个THEN中代表失败的)}
=> 即执行的是这句代码(因为是执行的失败态,所以返回的实例是失败态):return Promise.reject(reason);

说了这么多,到底理解不理解🙄,哈哈上代码🙈(内心是拒绝的,因为它肯定也会是很长的then链)

new Promise((resolve, reject) => {
 setTimeout(() => {
   reject(10); //状态:失败  值:10
 }, 1000);
}).then(result => {
 console.log(`成功:${result}`);
 return result * 10;
}).then(result => {
 console.log(`成功:${result}`);
 return result * 10;
}).then(null, reason => {
 console.log(`失败:${reason}`); //=>'失败:10'
 return reason * 2; 
 //代码执行没有报错,让当前实例状态:成功  值:20
}).then(null,reason => {
 console.log(`失败:${reason}`);
 return reason * 2;
}).then(result => {
 console.log(`成功:${result}`); //=>'成功:20'
});

四、Promise.all()

Promise.all([PROMISE1,PROMISE2,...]):等待所有的PROMISE实例都成功,整体才是成功的(返回新的PROMISE实例),只要有一个实例是失败的,整体实例就是失败的;

function fn1() {
 return new Promise(resolve => {
   setTimeout(_ => {
     resolve(10);
   }, 2000);
 });
}

function fn2() {
 return Promise.resolve(20);
}

function fn3() {
 return new Promise(resolve => {
   setTimeout(_ => {
     resolve(30);
   }, 500);
 });
}

Promise.all([fn1(), fn2(), fn3()]).then(results => {
 // 只有当三个PROMISE实例都成功的时候(等待最晚有结果的一个也是成功),才会触发执行,results会按照放置的顺序,存储着每一次获取的结果
 console.log(results); //=>[10, 20, 30]
});

五、Promise.race()

Promise.race() 同时发送多个请求,谁先有处理结果(不管结果是成功还是失败),就以谁的结果为主(哪怕是失败的)

六、【补充】:异常捕获

6.1 catch表示then 中只写一个reason方法

在项目中,我们会用 CATCH(REASON=>{})代替 THEN(NULL,REASON=>{}),效果是一模一样的(执行CATCH也会返回新的PROMISE实例,里面设置的方法是在实例为失败状态下执行的)

=>在项目中,我们一般THEN中放的是成功执行的,CATCH中放的是失败执行的

new Promise((resolve, reject) => {
 setTimeout(() => {
  reject(10);
 }, 1000);
}).then(result => {
 console.log(`成功:${result}`);
 return result * 10;
}).catch(reason => {
 console.log(`失败:${reason}`);
 return reason * 2;
});

6.2 异常捕获:try catch

为什么要用try catch

比如下面的代码:我们输出一个从来没有定义过的变量,会报错,下面的代码也不再执行,但是我们想让下面的代码继续执行,就需要用到try catch

console.log(n); //=>Uncaught ReferenceError: n is not defined 浏览器抛出异常信息,下面代码就不会再执行了
console.log('OK');

try catch

  • try:把可能会报错的代码放置到try中捕获异常(代码执行一但报错,控制台是不抛出异常的,不会影响后续代码的执行)
  • catchcatch中捕获到异常信息 (可以把信息上报给服务器)
try {
 // 把可能会报错的代码放置到TRY中捕获异常(代码执行一但报错,控制台是不抛出异常的,不会影响后续代码的执行)
 console.log(n);
} catch (error) {
 // CATCH中捕获到异常信息 (可以把信息上报给服务器)
 // console.log(error);
}
console.log('OK');