Promise代表承诺是一个类。它用于表示一个异步操作的最终完成或失败及其结果值。
当我们new一个Promise时,它里面必须有一个可执行函数executor作为参数,这个executor是立即执行的。executor函数中一般用来管理一个异步编程代码(当然同步代码也是可以的)并且会给executor函数传递resolve/reject这两个函数作为参数。异步操作成功执行resolve,修改promise实例状态为成功,传递的值就是 [[PromiseResult]](默认是undefined,成功的结果)。异步操作失败执行reject,修改promise实例的状态为失败,传递的值也是 [[PromiseResult]](失败的原因)。只要promise的状态发生改变那么再执行其任何操作状态不会发生改变。
let p = new Promise((resolve,reject)=>{ console.log(2); }); console.log(1);// 执行顺序是2 1 从中我们可以看到promise是同步的 // new Promise((resolve, reject)=>{resolve(value)}) resolve为成功,接收参数value,状态改变为fulfilled,不可再次改变 // new Promise((resolve, reject)=>{reject(reason)}) reject为失败,接收参数reason,状态改变为rejected,不可再次改变 // 若是executor函数报错 直接执行reject();
从上面代码可以看出p是Promise类的一个实例,这里面有一些内置的私有属性和公共方法从中我们可以看到有catch,finally,then方法。如下所示:
- 内置私有属性:
- [[PromiseState]]实例状态:pending / fulfilled / resolved
- [[PromiseResult]]实例的值
- 公共方法:
- then:为 Promise 实例添加状态改变时的回调函数。
- catch:.then(null,rejected)或.then(undefined,rejection)的别名,用于指定发生错误时的回调函数。
- finally:不管 Promise 对象最后状态如何,都会执行的操作。
从上面可以看出Promise有.then方法,.then方法里面也有两个函数(onfulfilledCallback,onrejectedCallback)作为参数,实例状态改变执行then方法时存放的两个方法中的某一 个方法执行:
- 当状态state为fulfilled,则执行onfulfilledCallback,传入this.value。当状态state为rejected,则执行onrejectedCallback,传入this.reason
- onfulfilledCallback,onrejectedCallback是函数,它们需要分别在fulfilled,rejected后被调用,value或reason依次作为他们的第一个参数
- 执行p.then时首先去把传递进来的onfulfilledCallback,onrejectedCallback存储在一个容器中然后去验证当前实例的状态
- 当实例状态为pending不做任何处理
- 如果已经变为fulfilled/rejected则会通知对应的回调函数执行,但是不是立即执行,而是将其放置在EventQueue中的微任务队列中
let p = new Promise((resolve, reject) => {
console.log(1); setTimeout(() => { resolve('ok');
console.log(4); }, 1000);
console.log(2); });
console.log(p); p.then(result => { console.log('成功', result); }, reason => { console.log('失败', reason); }) console.log(3);
在上面的代码中我们可以看到,代码自上而下运行:
- new Promise时生成一个promise实例p,立即执行executor函数,打印1;
- executor里面有一个setTimeout定时器,它是一个异步宏任务,将其放到EventQuene中;
- 代码向下,打印2;
- 继续向下,打印p,这时p是一个promise实例,它里面的状态没有发生改变还是pending;
- 执行p1.then注入两个方法,这两个方法会保存起来,放置在EventQueue中的异步微任务队列中;
- 执行同步代码,打印“3”;
- 1000ms后执行定时器里的回调函数,改变promise状态和值,状态从"pending" -> "fulfilled","value "-> “ok”。通过之前基于then存放的方法执行(异步微任务);
- 先要执行同步代码,打印4;
- 执行.then里面的onfulfilledCallback函数打印"成功 ok";
- 所以执行顺序是:” 1 “ =>” 2 “ => ”Promise{<pending>} “ =>” 3 “ =>” 4 “ => ”成功 ok“
let p1 = Promise.resolve('ok') let p2 = p1.then(result => { console.log('成功', result); //成功->'ok' }, reason => { console.log('失败', reason); }) console.log(p2); //Promise {<pending>} p2.then(result => { console.log('成功', result); }, reason => { console.log('失败', reason); }) console.log(p2);
执行.then返回一个全新的promise实例,所以可以采用链式写法,即then方法后面可以调用另外一个then。在p2执行中不论执行的是p1.then存放的onfulfilledCallback/onjectedCallback两个方法中哪一个
- 只要是方法执行不报错:如果方法报错[[PromiseState]]是rejected,[[PromiseResult]]报错的原因。
- 方法中返回的是一个全新的promise实例,那么“全新的promise实例”的成功和失败决定p2的成功与否;
- 如果返加的不是一个全新的promise实例,那么[[PromiseState]]是fulfilled,[[PromiseResult]]成功的结果;
- 如果onfulfilledCallback/onjectedCallback不传递,那么状态和结果都会“顺延/穿透”到下一个同等状态应该执行的回调函数上
Promise.reject('no') .then(result => { console.log('成功->', result); return 10; }/*reason=>{return Promise.reject(reason);}*/) .then(null, reason => { console.log('失败', reason); //失败 no }) Promise.resolve('ok') .then(null/* result => {return result;} */, reason => { console.log('失败->', reason); }) .then(result => { console.log('成功->', result); // 成功-> ok })
真实项目中在多个then链下,then方法基本都存放的是成功处理的事情,最后
Promise.resolve('ok') .then(result => { console.log('成功', result); //成功 ok return 10; }) .then(result => { console.log('成功', result); // 成功 10 return 20; }) .then(result => { console.log('成功', result); // 成功 20 return Promise.reject('no'); }) .then(result => { console.log('成功', result); return Promise.resolve('yes'); // Promise {<fulfilled>: undefined} }) // .then(null, reason => { // console.log('失败', reason); // 失败 no // }) //可以用catch来代替 .catch(reason => { console.log('失败', reason); // 失败 no })
promise里面还有其它一些常用方法,如promise.all([])要求数据中的每一项尽可能都是promise,它也是返回一个promise实例。等待所有的promise实例都成功整体返回的状态才是成功,只要有一个失败整体状态就是失败。而promise.race()看多个实例谁先处理完,先处理完成的状态(不论是失败还是成功)就是最后整体的状态。
在promise.all()方法中,最后的结果顺序和传递数组的顺序是一样的。处理过程如下:
function fn(interval) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(interval); }, interval); }) } let p1 = fn(3000); let p2 = fn(1000); //let p3 = Promise.resolve(0);
let p3 = Promise.reject("no"); Promise.all([p1, p2, p3]).then(results => {
// 结果的顺序和传递数组的顺序要保护一致 console.log(results) }).catch(reason => {
// 处理过程中,遇到一个失败则all立即为失败,结果就是失败的原因
console.log(reason);
})
在平时的项目中是解决异步编程, 我还们还要用到async/await。async/await可以让我们用一种更简洁的方式写出基于promise的异步行为,而不用刻意的链式调用promise。
async它是一个函数修饰符,可以控制函数返回promise实例。函数执行报错实例状态是失败,结果是报错原因;否则实例状态是成功,结果是return后面的值。如果函数内部做了异步捕获,则不是成功的状态。async一般配合await使用,await后面要放置一个promise,如果写的是其它如普通数字,那也会将其变成promise实例。await表达式会暂停整个async函数的执行进程并出让其控制权也就是说await中断函数体中其下面的代码执行,只有等待后面promise实例是成功态后才会把之前暂停的代码执行。如果后面的promise实例是失败的,则下面的代码不会再执行。总结来说,因为await是一个异步微任务,函数体中遇到await后面的代码执行并且await下面的代码会暂停执行放置在EventQueue的微任务队列中。
async function foo() { await 1 } // 等价于 function foo() { return Promise.resolve(1).then(() => undefined) }
function computed() { console.log(1); return new Promise(resolve => { setTimeout(() => { resolve(2); }, 1000); }) }; console.log(3); async function fn() { console.log(4); let result = await computed(); console.log(result); console.log(5); } fn(); console.log(6);
- 代码自上下而执行,先是定义computed函数;
- 第二步,主线程中,打印"3";
- 定义async fn()
- fn()函数执行代码向下,打印" 6 ";
- 打印“ 4 ”;
- let result = await computed(),这时需要先执行computed
- 打印" 1 ";
- 返回一个promise,设置了定时器,把定时器的回调函数放入到异步宏任务中1000ms后执行回调函数
- 再把await下面的代码存储到异步微任务中,等待promise状态变为成功就触发微任务执行
- 主线程为空,因为eventloop机制先执行异步微任务,但此时异步微任务依赖宏任务,所以先执行宏任务
- 1000ms,执行resolve(2),promise成功
- 执行微任务 打印 “ 2 ”;
- 代码向下打印“ 5 ”
- 所以最终结果是:3 4 1 6 2 5