# 概述
首先问大家个问题:
目前在前端开发中需要处理一些异步操作的时候,使用Promise的时候你是否觉得是一种理所当然的习惯性操作呢? 我们来看下 Promise 出现的时间列表: ES2015 规范新增 Promise ES2018 规范新增 Promise.finally ES2020 规范新增 Promise.allSettled ES2021 规范新增 Promise.any 也就是说在此之前是没有Promise这个东东的,那么没有Promise的时候我们又是怎么处理异步呢? 方法一:callback 大法 弊端: 1、回调地狱 2、调试链长可读性差
function getDataByCallback(onSuccess, onError) {
setTimeout(() => {
onSuccess && onSuccess(11);
}, 1000);
setTimeout(() => {
onError && onError(22);
}, 2000);
}
getDataByCallback(function onSuccess(result) {
~/~/ TODO
console.log('onSuccess: ', result);
}, function onError(error) {
~/~/ TODO
console.log('onError: ', error);
});
方法二:eventbus 大法
弊端:
1、代码跟踪调试麻烦;
2、效率低
//简易的发布订阅实现
var eventbus = {
events: {},
on(key, handler) {
if (typeof key !== 'string' || typeof handler !== 'function') {
throw Error('入参错误');
}
this.events[key] = this.events[key] || [];
this.events[key].push(handler);
},
trigger(key, params) {
var handlerList = this.events[key];
if (handlerList) {
for (let i = 0, len = handlerList.length; i < len; i++) {
handlerList[i](params);
}
}
}
}
var HANDLE_KEYS = {
GET_DATA: 'getData'
};
eventbus.on(HANDLE_KEYS.GET_DATA, function(result) {
console.log('结果:', result);
});
console.log('开始:');
setTimeout(() => {
eventbus.trigger(HANDLE_KEYS.GET_DATA, 123);
}, 2000);
方法三:事件触发类 依托交互、循环检查等等
弊端:
1、业务场景限制
2、效率稳定性不一
因此针对以上问题,以及现阶段前端的快速的发展进程,Promise的出现就很好理解了,当然也不仅仅只是Promise。只是本次我们主要讲Promise的基础和业务中总结的一些使用技巧
# 基础
Promise 英文意思为承诺,大家都知道。那么针对承诺,我们直观的关注点应该就是:
1、为什么事做的承诺
2、当前的承诺状态
3、什么时候兑现承诺
4、兑现承诺做什么,成功了怎么做?失败了怎么做?
针对这种事物的基本逻辑,Promise也将其做了相应的实现,这些大家可能也是基本都了解。
以[[Promise A+>>https://promisesaplus.com/#]]规范为例:
1、为什么事做的承诺(业务主体) executor
2、状态 Pending(等待态)、Fulfilled(成功态)、Rejected(拒绝态)
3、什么时候兑现承诺(执行回调), executor~-~-> resolve, reject
4、兑现承诺做什么(收集回调).then(onResolve, onReject) .catch(onReject) .finally(all)
# 知识点解析
1、executor 为同步执行,因此在业务开发中使用时,应该对具体的异步方法,是进行直接调用还是提供一个handler,这个需要优先考虑,如下两个场景
const promiseFactory = value => {
return new Promise((resolve, reject) => {
console.log('开始执行 ', item);
setTimeout(() => {
resolve(item);
}, Math.random() * 2000);
}).then(res => {
console.log('执行结束', res);
return res;
});
}
// 这里其实在构建promiseList的时候,每一个executor就已经在执行了
const executAll = (list) => {
Promise.all(list);
}
const valueList = [1, 2, 3];
const promiseList = valueList.map(promiseFactory);
executAll(promiseList);
// 因此可以将每个Promise异步业务进行handler化封装,后续调用时才执行
const executAll = (list) => {
Promise.all(list.map(func => func(~)~)~);
}
const valueList = [1, 2, 3];
const promiseList = valueList.map(value => () => promiseFactory(value));
executAll(promiseList);
OR
const executAll = (list) => {
const promiseList = list.map(promiseFactory);
Promise.all(list.map(func => func(~)~)~);
}
const valueList = [1, 2, 3];
executAll(valueList);
# 2、.then(), .catch(), .finally()用法以及注意项
Promise的then(onFulfilled, onRejected)方法主要收集Fulfilled和rejected状态下具体操作 注意事项: 1、promise.then()在调用是才会执行task.Enqueue方法将其放入microtask中 2、onRejected方法只能监听,reject触发和executor方法中抛出的错误,无法监听同级onFulfilled中出现的错误,但是可以监听上级的onFulffiled的错误,如:
new Promise((resolve, reject) => {
resolve(11)
// reject(111)
}).then(res => {
console.log('then', res)
dd
}, err => {
console.log('catch',err)
}).then(() => {
}, err => {
debugger
});
3、返回值问题,分为两种:
(1)、返回值非promise对象,那么会自动转换为Fulfilled状态,并且触发后续注册的onFulfiled回调
(2)、返回值为promise,会触发PromiseResolveThenableJob,分为两步生成一个job microtask,然后转接一个microtask到后面类似于then(onFulfilled)返回一个非promise的值类型
Promise.resolve()
.then(() => {
console.log('promise0')
return Promise.resolve();
}).then(() => {
console.log('sdfs')
})
Promise.resolve()
.then(() => { console.log('promise1') })
.then(() => { console.log('promise2') })
.then(() => { console.log('promise3') })
.then(() => { console.log('promise4') })
.then(() => { console.log('promise5') })
Promise的catch(onCatch),onCatch方法会捕获executor中的和之前的then,catch中的所有错误
Promise的finally(onCompleted),onCompleted回到Fulfilled和Rejected状态都会执行且不会接收数据
3、resolve, reject ,Promise.resolve Promise.reject用法以及注意项
resolve, reject为new Promise(executor)中executor的两个方法参数,类似事件的trigger 主要动作为修改状态、执行相应的回调。特殊情况为resolve参数为promise的情况会触发PromiseResolveThenableJob处理模式,会导致后续then收集的回调延后连个microtask执行,应为会生成一个job 和 同步结果的microtask。具体例子如后面的Promise.resolve讲解
Promise.resolve() 直接返回一个Fulfilled状态的promise实例,其中可接受4种类型参数:
1、参数为promise实例
const promise1 = new Promise((resolve, reject) => {
resolve(1);
});
const promise2 = Promise.resolve(promise1);
console.log(promise1 === promise2); ~/~/ true
2、参数为thenable,带有then方法额对象,Promise.resolve() 方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then()方法
3、参数为一个原始值或者不带参数,那么Promise.resolve()返回一个新的状态为Fulfilledd的promise对象 针对resolve参数传递Promise对象,从而从而走A+规范PromiseResolveThenableJob的问题。特殊例子如下:
new Promise(resolve => {
let resolvedPromise = Promise.resolve()
resolve(resolvedPromise)
}).then(() => {
console.log('resolvePromise resolved')
})
Promise.resolve()
.then(() => { console.log('promise1') })
.then(() => { console.log('promise2') })
.then(() => { console.log('promise3') })
//但是平时业务使用中,我们不太会去关注两个无关联性的promise的回调执行顺序。
// Promise.reject方法执行得到一个Rejected状态的Promise对象
const rejectedPromise = Promise.reject();
# 业务场景中的特殊用例
# 1、业务权限转接
场景: 1、转接、封装其他的异步操作,最常见的需求 2、替换、缓存、传递resolve,reject操作方法,从而达到控制异步操作响应、转移操作能力的目的
const checkOptions = (options = {}) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (options.ok) resolve(options.ok);
else reject('错误');
}, 1000);
});
}
const getData = (options) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功')
}, 1000);
})
}
const getDataHandler = (options = {}) => {
return checkOptions(options).then(getData)
}
getDataHandler({ ok: false }).then(res => {
console.log(res)
}).catch(err => {
console.log('error', err);
});
# 2、promise列表,队列执行
场景:针对需要顺序执行的业务动作,其中包括promis function。如:有关联先后循序的连续型异步业务操作
/*
~* promise批量串行处理
*
~* @param {array} [arr=[]]
~* @param {function} onItemFinish 单项回调函数
~* @param {boolean} [isFinishWhenError=false] 为false不会因为单项错误中断串行执行,单项的rejected不抛出,直接放入fulfilled中捕获
~* @returns {promise}
*/
const promiseQueue = (arr = [], onItemFinish, isFinishWhenError = false) => {
const list = [].concat(arr);
const isType = hand => Object.prototype.toString.call(hand);
if(isType(list) !== '[object Array]') throw new TypeError('arr must is a array');
const isFunction = (func) => isType(func) === '[object Function]';
const isPromise = (func) => isType(func) === '[object Promise]';
return list.reduce((nextPromise, currentPromise, index) => {
return nextPromise.then(result => {
if (!isPromise(currentPromise) && !isFunction(currentPromise)) throw new TypeError('item must is a function or a promiseFunction');if (!isPromise(currentPromise) && !isFunction(currentPromise)) throw new TypeError('item must is a function or a promiseFunction');
if(isFunction(currentPromise)) {
currentPromise = currentPromise();
}
if (isPromise(currentPromise)) {
return currentPromise.then((res) => {
result.push(res);
return result;
}).catch(err => {
result.push(err);
return isFinishWhenError ? Promise.reject(result) : result;
});
} else {
result.push(currentPromise);
return Promise.resolve(result);
}
}).then(function(result) {
onItemFinish && onItemFinish(result.slice(-1)[0], index);
return result;
}).catch(function(result) {
onItemFinish && onItemFinish(result.slice(-1)[0], index);
return Promise.reject(result);
});
}, Promise.resolve([]));
};
let promises = [1, 2, 3, 4, 5, 6].map((item, index) => {
return () => new Promise((resolve, reject) => {
console.log('发送请求', item)
setTimeout(() => {
~/~/ if(item !== 3) resolve(item)
~/~/ else reject('222');
resolve(item)
}, Math.random() * 100);
});
});
const itemCallback = (res) => {
console.log('请求响应', res);
}
promiseQueue(promises, itemCallback, false).then(res => {
console.log('==', res);
});
# 3、前置请求,缓存后续队列
1、前置请求场景,如:站点配置、权限校验等等,需要等待某个异步操作完成以后才能执行后续请求,但是又不能限制业务,要做到业务无感知,可并发使用的情况
2、单个业务场景,如:上传文档操作,需要先项服务端申请token然后才能执行上传、转发等待多个并行异步操作
/*
* @Author: daipeng7
* @Date: 2021-12-29 10:26:48
* @LastEditTime: 2021-12-29 14:13:39
* @LastEditors: daipeng7
* @Description: Promise 缓存使用场景demo代码
* 1、前置请求场景,如:站点配置、权限校验等等,需要等待某个异步操作完成以后才能执行后续请求,但是又不能限制业务,要做到业务无感知,可并发使用的情况
* 2、单个业务场景,如:上传文档操作,需要先项服务端申请token然后才能执行上传、转发等待多个并行异步操作
*/
const isType = hand => Object.prototype.toString.call(hand);
const isFunction = (func) => isType(func) === '[object Function]';
const isPromise = (func) => isType(func) === '[object Promise]';
class CacheRequest {
constructor() {
this.cacheList = [];
this.cache = false;
}
invok(func) {
// 缓存
if (this.cache) return this._setCache(func);
// 非缓存
if (!isFunction(func) && !isPromise(func)) return Promise.resolve(func);
if (isFunction(func)) func = func();
if (isPromise(func)) return func;
else return Promise.resolve(func);
}
// 设置缓存,提供了一个对外等待的wrap Promise对象,并且将resolve和reject闭包的方式存入了缓存,等待缓存被执行的时候再来响应调用方
_setCache(func) {
console.log('==收集缓存==');
let handler;
// 缓存执行handler,最好不要直接缓存func
if (isFunction(func)) handler = func;
else if (isPromise(func)) handler = () => handler;
else handler = () => Promise.resolve(handler);
const wrapPromise = new Promise((resolve, reject) => {
this.cacheList.push(() => {
return handler().then(resolve).catch(reject);
});
});
return wrapPromise;
}
// 执行cache
invokCache() {
if (this.cacheList.length && !this.cache) {
console.log('开始执行缓存');
const invokList = [ ...this.cacheList ];
this.cacheList = [];
invokList.forEach(this.invok.bind(this));
}
}
}
const request = new CacheRequest();
// 对外调用方法
const requestHandler = (fun, prev = false) => {
const _promise = request.invok(fun).then(res => {
request.cache = false;
request.invokCache();
return res;
});
if (prev) request.cache = prev;
return _promise;
}
// 测试数据
const configRequest = () => {
return new Promise((resolve, reject) => {
console.log('执行config');
setTimeout(() => {
resolve('config');
}, 1000);
}).then(res => {
console.log('响应config', res);
return res;
});
}
const asyncList = [1,2,3,4].map(item => {
return () => new Promise((resolve, reject) => {
console.log('执行后续异步操作', item);
setTimeout(() => {
resolve(item);
}, Math.random() * 2000);
}).then(res => {
console.log('响应后续异步操作', res);
return res;
});
});
// 并发调用
requestHandler(configRequest, true);
asyncList.forEach(requestHandler);
# 总结
Promise使用中的关注点是针对业务封装、转接、执行控制。这三点灵活的组合可以很好的解决业务中可能需要交互、定时器等辅助实现带来的不稳定问题,同时对于代码的可读性也会变得更高。