什么是Promise
Promise 是一个构造函数,是用来解决JS异步编程(回调地狱)的一种解决方案,怕有些小伙伴不清楚,所以我在本章最后写了案例o.O,下面我们先来了解 Promise 的一些基础知识。
Promise 特点
对象的状态不受外界的影响
Pending (进行中)Fulfilled (已成功)Rejected (已失败)复制代码
状态的固化(状态一旦发生改变就不会再改变)
Pending -> Fulfilled Pending -> Rejected复制代码
Promise 创建
构造函数接收一个 executor的函数作为参数。该函数的两个参数 resolve 和 reject,它们都是函数,用来改变 Promise 对象的状态。resolve是将Promise对象的状态由‘进行中’ 变为 ‘成功’Pending -> Fulfilled,在异步操作成功时调用,并将成功的结果作为参数传递给对应的回调函数。reject是将Promise对象状态由‘进行中’ 变为 ‘失败’Pending -> Rejected,在异步操作失败时调用,并将失败的信息作为参数传递给对应的回调函数。
executor函数什么时候执行呢?
let promise = new Promise((resolve, reject) => { console.log(1); }) console.log(2); // 1 2 // 在 new 创建 promise 实例时立即调用。复制代码
在Promise实例生成后,可以通过调用then()方法来绑定异步操作成功/失败的回调函数。then()方法接收两个回调函数作为参数,第一个参数必选,Promise 对象成功状态的回调函数,第二个参数可选,Promise 对象失败状态的回调函数。
let promise = new Promise((resolve, reject) => { setTimeout(() =>{ Math.random() * 100 > 60 ? resolve('及格') : reject('不及格'); }, 300) }) promise.then((result) => { console.log(result); }, (reason) => { console.log(reason); })复制代码
执行顺序,我们发现输出的顺序是 1 -> 3 -> 2,Promise 新建后立即执行,所以先输出 1,然后通过resolve改变Promise对象的状态来调用then()方法中对应的回调函数是异步操作,所以会等待当前脚本所有的同步任务执行完成后才执行,所以先打印3,最后才是2。
let promise = new Promise((resolve,reject) => { console.log(1); resolve(2); }) promise.then((res) => { console.log(res); }) console.log(3); // 1 3 2复制代码
与定时器异步的区别
首先先简单提一下JS代码的执行过程:同步的进入主线程,异步会的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。上述过程会不断重复,也就是常说的Event Loop(事件循环)。
JS 异步代码中,分为宏任务与微任务,他们分别有自己的任务队列。微任务:promise、process.nextTick();宏任务:setTimeout、setInterval, Ajax,DOM事件。这里就存在一个优先级的问题,当主线程空闲时间,会先检查微任务队列,然后再走宏任务队列。所以下面的代码执行顺序就是 2 -> 4 -> 3 -> 1
setTimeout(() => { console.log(1); }, 30) let promise = new Promise((resolve, reject) => { console.log(2); resolve(3); }) promise.then((res) => { console.log(res); }) console.log(4);复制代码
then方法链式调用
then()方法的作用是为 Promise 实例添加成功和失败状态的回到函数,会返回一个新的 Promise 实例,所以then()方法后面可以继续跟另一个then()方法进行链式调用。
let promise = new Promise((resolve, reject) => { resolve(1); }) promise.then((result) => { console.log(result); return 2; }, (reason) => { console.log(reason); }) .then((result) => { // 上一次then的返回值作为这次的参数,没有则为 undefined console.log(`then2: ${result}`); })复制代码
如果前一个then()方法中的回调返回的是一个 Promise 实例,这时下一个then()方法中的回调会根据前一个的 Promise 实例的状态来进行调用。
let p = new Promise((resolve, reject) => { resolve('success'); }) p.then((result) => { console.log(result); // -> 'success' return new Promise((resolve, reject) => { reject(`error-.-`); }) }) .then((result) => { console.log(result); // -> 不走这里因为上一次返回的 Promise 状态为 Rejected }, (reason) => { console.log(reason); // -> 'error-.-' })复制代码
catch方法
用于指定发生错误时的回调函数,就等同于then()方法的第二个回调函数。
let promise = new Promise((resolve, reject) => { resolve(data); }) // 因为then方法第一个参数是必选项,又用不到所以就填一个null promise.then(null, (reason) => { console.log(reason); }) // === promise.then((res) => { console.log(res); }) .catch((err) => { console.log(err); })复制代码
状态固化
Promise 对象状态一旦发生改变,就不会再变。
let promise = new Promise((resolve, reject) => { resolve('Fulfilled'); // 将状态改为 Fulfilled console.log(data); // 再抛出错误也不会改变状态 }) promise.then(res => console.log(res)) .catch(err => console.log(err)); // 捕获不到 // -> Fulfilled复制代码
状态依赖
当前 p2 的状态依赖于 p1 的状态,就会导致自身的状态失效。
let p1 = new Promise((resolve, reject) => { setTimeout(() => { reject('fail') }, 1000) }) let p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(p1) }, 500) }) p2.then(res => console.log(res)) .catch(err => console.log(err)); // -> fail复制代码
all和race
都是 Promise 构造器上的方法,Promise.all(),可以将多个 Promise 实例包装成一个新的Promise 实例。成功与失败的返回值是不同的,当所有的都成功时返回的结果是一个数组,失败时返回最先触发失败状态的错误信息。
注意: Promise.all() 获取的成功结果的数组里的数据与传入时的顺序是一致的。
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('peace'); }, 1000) }) let promise2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('and'); }, 2000)}) let promise3 = new Promise((resolve, reject) => { setTimeout(() => { resolve('love'); }, 300) }) let p = Promise.all([promise1, promise2, promise3]); console.log(p); p.then(res => console.log(res)) // -> ["peace", "and", "love"] .catch(err => console.log(`reject: ${err}`)); 复制代码
Promise.race(),与Promise.all()的用法相同,但是功能不同,它是获取最快返回结果的那一个,无论结果本身是成功态还是失败状态。
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('peace'); }, 1000) }) let promise2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('and'); }, 2000)}) let promise3 = new Promise((resolve, reject) => { setTimeout(() => { resolve('love'); }, 300) }) let p = Promise.race([promise1, promise2, promise3]); console.log(p); p.then(res => console.log(res)) // -> love .catch(err => console.log(`reject: ${err}`));复制代码
解决回调地狱问题
// 在无 Promise 之前 node环境读取文件 const fs = require('fs'); // 这种代码非常的不好维护 fs.readFile('./name.txt', 'utf-8', (err, data) => { if(data){ fs.readFile(data, 'utf-8', (err, data) => { if(data){ fs.readFile(data, 'utf-8', (err, data) => { console.log(data); }) } }) } }) // Promise化管理 function readFile (path) { return new Promise((resolve, reject) => { fs.readFile(path, 'utf-8', (err, data) => { if(data){ resolve(data); } }) }) } readFile('./name.txt').then(data => return readFile(data)) .then(data => return readFile(data)) .then(data => console.log(data)) // 将传入的函数Promise化 function promisify (fn) { return function (...args) { return new Promise((resolve, reject) => { fn(...args, (err, data) => { if(err){ reject(err); }else{ resolve(data); } }) }) } }复制代码