promise-异步编程利器
文章通过实现一个简易的类来理解Promise原理
,并将Promise和传统回调
对比,展示了Promise在异步编程中的优势
,最后是介绍了Promise在实际开发中的应用
。
一. 概念
Promise将异步操作
和回调
进行解耦,并通过执行状态
将两者关联。异步操作结束后把状态通知Promise,由Promise负责触发回调函数。
二. Promise原理
1. 状态变更
输入:
let p0 = new Promise((resolve, reject) => {}) console.log('p0', p0) let p1 = new Promise((resolve, reject) => { resolve('成功') }) console.log('p1', p1) let p2 = new Promise((resolve, reject) => { reject('失败') }) console.log('p2', p2) let p3 = new Promise((resolve, reject) => { throw('报错') }) console.log('p3', p3)复制代码
输出:
结论:
刚创建的Promise对象处于pending状态。
当异步操作完成后,调用resolve/reject变更状态为fulfilled/rejected。
执行函数发生异常(同步异常)时,变更状态为rejected。
实现:
class MyPromise { constructor(executor) { this.initValue(); // 由于resolve/reject是外部函数executor调用的,所以必须将this硬绑定为当前MyPromise对象 this.initBind(); try { // 执行传进来的函数 executor(this.resolve, this.reject); } catch (e) { // 捕捉到错误直接执行reject this.reject(e); } } initBind() { this.resolve = this.resolve.bind(this); this.reject = this.reject.bind(this); } initValue() { this.PromiseResult = null; this.PromiseState = 'pending'; } resolve(value) { // 状态只能由pending转换为fulfilled/rejected if (this.PromiseState == 'pending'){ this.PromiseState = 'fulfilled'; this.PromiseResult = value; } } reject(reason) { // 状态只能由pending转换为fulfilled/rejected if (this.PromiseState !== 'pending'){ this.PromiseState = 'rejected'; this.PromiseResult = reason; } } }复制代码
2. 执行回调
输入/输出:
// 立刻输出 ”resolve=成功“ const p1 = new Promise((resolve, reject) => { resolve('成功'); }).then(res => console.log('resolve=',res), err => console.log('reject=',err)) // 1秒后输出 ”reject=失败“ const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject('失败'); }, 1000) }).then(res => console.log('resolve=',res), err => console.log('reject=',err))复制代码
结论:
then接收两个参数:
成功回调
和失败回调
当Promise状态为
fulfilled
执行成功回调
,为rejected
执行失败回调
通过多次调用then可以
注册多个回调函数
,注意区分链式调用
实现:
通过then注册回调时,如果Promise状态为fulfilled/rejected,则执行回调函数;
如果Promise状态为pending,则先保存回调函数,等异步操作结束后再执行回调。
initValue() { ... // 保存回调函数。 this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; } resolve(value) { ... // 状态变更为fulfilled,执行保存的成功回调 while (this.onFulfilledCallbacks.length) { this.onFulfilledCallbacks.shift()(this.PromiseResult); } } reject(reason) { ... // 状态变更为rejected,执行保存的失败回调 while (this.onRejectedCallbacks.length) { this.onRejectedCallbacks.shift()(this.PromiseResult); } } then(onFulfilled, onRejected) { // 参数校验,确保一定是函数 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }; if (this.PromiseState === 'fulfilled') { // 执行fulfilled回调 onFulfilled(this.PromiseResult); } else if (this.PromiseState === 'rejected') { // 执行rejected回调 onRejected(this.PromiseResult); }else if (this.PromiseState === 'pending') { // Promise为pending状态,暂时保存两个回调 this.onFulfilledCallbacks.push(onFulfilled); this.onRejectedCallbacks.push(onRejected); } }复制代码
3. 链式调用
输入/输出
// 链式调用 输出 200 const p3 = new Promise((resolve, reject) => { resolve(100); }).then(res => 2 * res) .then(res => console.log(res)) // 链式调用 输出300 const p4 = new Promise((resolve, reject) => { resolve(100); }).then(res => new Promise((resolve, reject) => resolve(3 * res))) .then(res => console.log(res))复制代码
结论
then方法本身会返回一个新Promise对象。
如果回调函数返回值是Promise对象,则新Promise对象状态由该Promise对象决定。
如果回调函数返回值不是Promise对象,则新Promise对象状态为成功。
实现
then(onFulfilled, onRejected) { ... var thenPromise = new MyPromise((resolve, reject) => { const resolvePromise = cb => { try { const x = cb(this.PromiseResult) if(x === thenPromise){ // 自己等待自己完成,循环等待:在then回调中返回了then的返回值。 reject(new TypeError('Chaining cycle detected for promise')); } if (x instanceof MyPromise) { // 如果返回值是Promise对象,则新Promise状态由该Promise决定。 x.then(resolve, reject); } else { // 非Promise就直接成功 resolve(x); } } catch (err) { // 处理报错 reject(err); } } if (this.PromiseState === 'fulfilled') { // 如果当前为成功状态,执行第一个回调 resolvePromise(onFulfilled); } else if (this.PromiseState === 'rejected') { // 如果当前为失败状态,执行第二个回调 resolvePromise(onRejected); } else if (this.PromiseState === 'pending') { // 如果状态为待定状态,暂时保存两个回调 this.onFulfilledCallback = resolvePromise(onFulfilled); this.onRejectedCallback = resolvePromise(onRejected); } }) // 返回这个包装的Promise return thenPromise; }复制代码
4. 调用时序
输入输出
setTimeout(()=>{console.log(0)},0); const p = new Promise((resolve, reject) => { console.log(1); resolve() }).then(() => console.log(2)) console.log(3) // 输出顺序是 1 3 2 0复制代码
结论
即使Promise的状态被立刻更新为fulfilled,回调函数也不会被立刻执行。
回调函数是在事件循环的微任务中执行的。
因为回调本来就是异步的,放在微任务执行可以让后面的同步代码尽快被执行。
实现
const resolvePromise = cb => { setTimeout(() => { // 执行回调... }) }复制代码
三. Promise VS 传统回调
1. 回调地狱
面对执行多重异步操作
的场景,传统回调需要将回调函数作为参数传入
,存在“回调地狱”问题。而Promise将异步操作和回调函数解耦
了,不需要在一开始就传入回调函数。
// 传统回调 实现多重异步操作 doSomething(function(result) { doSomethingElse(result, function(newResult) { doThirdThing(newResult, function(finalResult) { console.log('Got the final result: ' + finalResult); }, failureCallback); }, failureCallback); }, failureCallback); // Promise 实现多重异步操作 doSomething().then(function(result) { return doSomethingElse(result); }) .then(function(newResult) { return doThirdThing(newResult); }) .then(function(finalResult) { console.log('Got the final result: ' + finalResult); }) .catch(failureCallback);复制代码
2. 异常捕获
注:这里的异常是指 回调函数异常,而不是异步操作异常。
// 下面是一个典型的超时回调,由于timeoutCallback是在异步操作中被调用的,所以try catch无法捕获异步异常。 try{ setTimeout(()=>timeoutCallback("3 seconds passed"), 3000); }catch(err){ // 这里是无法捕获到 timeoutCallback异常的。 console.log(err); } // Promise负责调用回调函数。当异步操作结束Promise状态变更后,Promise会在调用回调函数时加上try catch。 const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); wait(3000).then(() => timeoutCallback("3 seconds passed")).catch(err=>{console.log(err)});复制代码
四. Promise应用
1. 创建已完成的Promise
Promise.resolve() 和 Promise.reject() 复制代码
2. 封装传统回调
由于传统回调存在 回调地狱
和缺乏异步异常捕获
等问题,所以使用Promise对已存在的传统回调进行封装。
// 下面是一个典型的超时回调,由于timeoutCallback是异步执行的,所以无法捕获异步回调中的异常。 try{ setTimeout(()=>timeoutCallback("3 seconds passed"), 3000); }catch(err){ // 这里是无法捕获到 timeoutCallback异常的。 console.log(err); } // Promise可以将异步操作和回调分开,当异步操作完成后调用回调时,会自动在回调函数加上try catch。 const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); wait(3000).then(() => timeoutCallback("3 seconds passed")).catch(err=>{console.log(err)});复制代码
3. 执行多个异步操作
// 并行多个异步操作,然后等所有操作完成后进入下一步操作 Promise.all([promise1, promise2, promise3]) .then(([result1, result2, result3]) => { // next step }); .catch(err){ // 当任意一个操作reject时,进入catch,并返回对应reject信息。 } // 按顺序执行多个异步操作(链式调用) [promise1, promise2, promise3].reduce((pre, cur) => pre.then(()=>{return cur}), Promise.resolve()) .then(result3 => { /* use result3 */ }).catch(err=>{ // 当任意一个操作reject时,进入catch,并返回对应reject信息。 }); // 在es2017中,也可以使用async/await对上面代码进行优化 let result; try{ for (const pro of [promise1, promise1, promise1]) { result = await pro(result); } }catch(err){ result = err; }
作者:Ys561
链接:https://juejin.cn/post/7048570128249651231