笔者公司前端小组,线上出现了由于Promise用错了而致使的问题。 本文的出发点不只是了解Promise,主要目的是跟着特性去写一个Promise。 因此前提是你已经掌握了Promise的基本特性,若是没有接触过的话,请点击学习前端
链式调用。git
内部三种状态。es6
内部三种状态分别为pending、fullfiled、rejected,初始化状态为pending。状态变化能够从pending转化fullfiled或rejected。无其余转化方式。github
解决ajax回调地狱。ajax
ajax回调地狱,本质是但愿不可控制的异步变成同步形式。如:npm
ajax1({...,success(res1){
...
}})
ajax2({...,params:{res1},success(res2){ // error no res1
...
}})
复制代码
当咱们的ajax2须要用到ajax1的时候,咱们不得不使用嵌套式:数组
ajax1({...,success(res1){
ajax2({...,params:{res1},success(res2){
/// doing something
}})
}})
复制代码
这种写法的最大问题就是当嵌套层数不少当时候,代码会变得难以维护。
promise
那么 how is Promise的写法 ajax改造:bash
p1 = function() {
return new Promise(function(resolve){
ajax1({...,success(res1){
resolve(res1)
}})
})
}
p2 = function(){
return new Promise(function(resolve){
ajax2({...,params:{res1},success(res2){
/// doing something
}})
})
}
复制代码
那么最终的写法则变成异步
p1().then(res=>{
return p2()
}).then(res2=>{
// doing something
})
复制代码
这里根据Promise A+ 把接下来的几个定义函数作在约定。
1.new Promise().then(),传递的函数分别叫 onFulfilled、onRejected
因此咱们最早开始的就是传递一个函数
function Promise(executor) {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
}
复制代码
promise有三种状态,能够用ts的枚举
enum pStatus {
pending = 'pending',
fulled = 'fullfilled',
rejected = 'rejected'
}
复制代码
而后咱们须要定义一些属性。
function Promise() {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
this.status = pStatus.pending; // 默认状态
this.resovlecbs = []; // 回调的resolve函数 主要来自于Promise.prototype.then
this.rejectcbs = []; // 回调的reject函数 主要来自于Promise.prototype.then
this.value; // 记录的resolve值
this.error; // 记录的reject值
}
复制代码
咱们知道Promise传递的函数,是直接会在主线程的执行的,因此咱们须要直接执行它。
function Promise() {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
this.status = pStatus.pending; // 默认状态
this.resovlecbs = []; // 回调的resolve函数 主要来自于Promise.prototype.then
this.rejectcbs = []; // 回调的reject函数 主要来自于Promise.prototype.then
this.value; // 记录的resolve值
this.error; // 记录的reject值
try {
executor(resolve,reject); // 传递的函数的执行。
} catch (error) {
reject(error); // 捕获的异常会直接执行reject。
}
}
复制代码
首先须要说明的是链式的结构的原理是不断返回新的Promise,也就是说then的结果是
Promise.prototype.then = function() {
return new Promise(function(resolve,reject){
xxx...
})
}
复制代码
首先咱们须要明确,Promise具体特性是什么?
1.then 传入的值分别是 resolve的回调和 reject状态回调。
2.传递值,将上一个then的值一直往下传。
3.符合同层先来先到,异层一定上层先执行的策略。
为了了解第三个特性的详细意思以前,让咱们看一个例子:
var p1 = new Promise(function(resolve,reject){
resolve('p1')
});
var p2 = new Promise(function(resolve,reject){
resolve('p2')
});
p1.then(()=>{
console.log('p11')
}).then(()=>{
console.log('p12')
})
p2.then(()=>{
console.log('p21')
}).then(()=>{
console.log('p22')
})
复制代码
相信你们都知道顺序为 p11 => p21 => p12 => p22。缘由的话涉及到宏微任务的特性,请参考这篇文章,点击学习。 想必你们已经明白第三点了。
那么如何实现?
分步骤:
1) 传递给Promise的函数,完成后咱们才会执行then传递的函数。也就是
new Promise(function(resolve,reject){
resolve('xxx') // (1)只有执行完这个才会执行后面then的 onFulfilled函数
}).then(function(){
...xxx // (2)这是第二步
})
复制代码
因此then传递onFulfilled函数和onRejected函数都是在resolve中执行的。因此then其实只是去保存then传递的函数而已,而保存的地方则是Promise主函数内部的resolvecbs和rejectcbs这两个数组。
我以为可能会有人问为何会是数组?
由于你可能会这么写:
var p = new Promise(...);
p.then(function(){ ... },...);
p.then(function(){ ... },...);
p.then(function(){ ... },...);
复制代码
这种非链路,其实都是把onFullfilled,保存到Promise内部,因此须要数组。
而后就是Promise内部到resolve函数和reject函数。这两个函数会作为 用户传入的函数的参数传入。 本质内部就是去遍历执行reslovecbs的函数项。而且改变状态,还有就是将传入的值记录下来,这些值会传给onFullfilled,并由onFullfilled决定是否要继续传递下去。也便是:
then(function onFullfilled(value){ // value 来自于resolve传递的参数。
return value // return 则表示续传 下一个then是否能拿到。
})
复制代码
咱们添加下,大体以下:
function Promise() {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
this.status = pStatus.pending; // 默认状态
this.resovlecbs = []; // 回调的resolve函数 主要来自于Promise.prototype.then
this.rejectcbs = []; // 回调的reject函数 主要来自于Promise.prototype.then
this.value; // 记录的resolve值
this.error; // 记录的reject值
const resolve = (value:object)=>{ // resolve作的三件事
this.value = value; // 记录值 then 的 onFullfilled会用它
this.resovlecbs.forEach((item:Function)=>{
item(value); // 这个就是 onFullfilled函数,会用上面的value
})
this.status = pStatus.fulled; // 把状态改变为 fullfilled
}
// ... reject同理
try {
executor(resolve,reject);
} catch (error) {
reject(error);
}
}
复制代码
resolve中执行的是resolveCbs数组存放的函数。而这些函数是来自于then推送的。 可是值得注意的是,函数除了执行then传递的onFullfiled函数和onRejected函数,还要将这两个返回的值,传递下去,因此要执行下一个Promise的resolve
,由于resolve的第一个特性就是记录值。 因此then是这样的。
Promise.prototype.then = function (onFullfilled:Function=noop,onRejected:Function=noop) {
let scope = this;
return new Promise(function(resolve = noop,reject = noop){
scope.resovlecbs.push((value)=>{
handlerRes(onFullfilled,value,resolve);
})
scope.rejectcbs.push((error)=>{
handlerRes(onRejected,error,reject);
})
});
}
export function handlerRes(handler,message,next){
let res
if(isFunc(handler)){
res = handler(message);
}
next(res); // 执行下一个函数的resolve
}
复制代码
能够看到这里是把 then传递的函数onFullfilled和onRejected分别推入 实例的 resovlecbs 数组和 rejectcbs()达到resolve和onFullfilled的同步执行的效果。
且不只是onRresolved被执行,同时被执行的还有下一个Promise的 resolve。
这样已经实现了then 链的顺序执行了。
对于构造函数new Promise(),的几个步骤是 建立一个空对象,并将Promise内部执行的全部属性都挂载到这个对象上。也就是this的全部属性。
传递都效果图以下:
可是上面的写法会有两个问题:
1.没法达到前面说的符合同层先来先到,异层一定上层先执行的策略。
,这种效果,正式event loop的队列。 因此咱们可使用微任务或宏任务。 这里是了简化代码结构使用setTimeout来模拟,若是感兴趣能够去了解下这个npm库asap
,点击这里
2.目前咱们的then函数的写法是直接把函数推入到resolvecbs数组,等待resolve去执行,可是这种方式不hack,若是咱们先执行了resolve后,咱们在执行then。好比:
var p1 = new Promise(function(resolve,reject){
resolve('p1')
});
p1.then(()=>{
console.log('p11')
}).
复制代码
这时候咱们会先执行resolve, 完成了resolvecbs的遍历执行,而后才去经过then,对resolvecbs进行搜集。name后面搜集的函数就永远不会执行了。因此咱们必须判断状态。
hack写法:
Promise.prototype.then = function (onFullfilled:Function=noop,onRejected:Function=noop) {
let scope = this;
return new Promise(function(resolve = noop,reject = noop){
if(scope.status === pStatus.pending) { // pending则等待执行
scope.resovlecbs.push((value)=>{
handlerRes(onFullfilled,value,resolve);
})
scope.rejectcbs.push((error)=>{
handlerRes(onRejected,error,reject);
})
} else if(scope.status===pStatus.fulled) { // fullfilled则直接执行
handlerRes(onFullfilled,scope.value,resolve);
} else { // rejectd 直接执行
handlerRes(onRejected,scope.error,reject);
}
});
}
复制代码
这里将多种状况考虑进入了,然而你以为已经结束了吗? 咱们来看下Promise A+怎么说的。
什么意思? 让咱们看一个例子:
new Promise(function(resolve){
resolve('test')
}).then().then(value=>{
console.log(value)
})
复制代码
上述状况,会传递吗?
答案是会。 这是Promise A+的标准。
因此咱们在then中必须兼容了。让咱们再次改造下then函数。
Promise.prototype.then = function (onFullfilled:Function,onRejected:Function) {
let scope = this;
return new Promise(function(resolve = noop,reject = noop){
const resolveHandler = function(value){
if(isFunc(onFullfilled)) {
handlerRes(onFullfilled,value,resolve);
} else {
resolve(value)
}
}
const rejectHanlder = function(error) {
if(isFunc(onRejected)){
handlerRes(onRejected,error,resolve);
} else {
reject(error);
}
}
try {
if(scope.status === pStatus.pending) {
scope.resovlecbs.push((value)=>{
resolveHandler(value)
})
scope.rejectcbs.push((error)=>{
rejectHanlder(error);
})
} else if(scope.status===pStatus.fulled) {
resolveHandler(scope.value);
} else { // rejectd
rejectHanlder(scope.error);
}
} catch (error) {
reject(error);
}
});
}
function handlerRes(handler,message,next){
let res
if(isFunc(handler)){
res = handler(message);
}
next(res); // 执行下一个函数的resolve
}
复制代码
好了,到了这里基本已经完成大部分了,然而,关于回调地狱的问题依然没有解决。让咱们看看咱们是怎么处理回调地狱的。
promise1.then(function(){
return promise2
}).then(value=>{
console.log(value) // 应该须要拿到promise2的结果
})
复制代码
目前咱们处理then的onFullfilled函数的结果是在handlerRes这个函数中进行的,因此咱们必须对这个函数进行改造,来适应return类型为promise的处理。
function handlerRes(handler,message,nextResolve,nextReject,Promise){
let res
if(isFunc(handler)){
res = handler(message);
}
if(res && res instanceof Promise) {
if(res.status===pStatus.pending){
res.then(value=>{
nextResolve(value)
},err=>{
nextReject(err)
})
}
} else {
nextResolve(res);
}
}
复制代码
上面已经添加了对Promise的处理,这样ok了吗? 若是promise2也是个深层次的promise则会出问题。 如promise2.then(value=>{}); value若是是promise
的实例, 这个时候,咱们的handlerRes仍是会问题的。
因此咱们须要递归的处理,让咱们改造下:
export function deepGet(res,Promise2,nextResolve,nextReject){
if(res && res instanceof Promise2) {
if(res.status===pStatus.pending){
res.then(value=>{
deepGet(value,Promise2,nextResolve,nextReject)
},err=>{
nextReject(err)
})
}
} else {
nextResolve(res);
}
}
export function handlerRes(handler,message,nextResolve,nextReject,Promise2){
let res
if(isFunc(handler)){
res = handler(message);
}
deepGet(res,Promise2,nextResolve,nextReject)
}
复制代码
完整的代码示例以下:
Promise2.prototype.then = function (onFullfilled:Function,onRejected:Function) {
let scope = this;
return new Promise2(function(resolve = noop,reject = noop){
const resolveHandler = function(value){
if(isFunc(onFullfilled)) {
handlerRes(onFullfilled,value,resolve,reject,scope.constructor);
} else {
resolve(value)
}
}
const rejectHanlder = function(error) {
if(isFunc(onRejected)){
handlerRes(onRejected,error,resolve,reject,scope.constructor);
} else {
reject(error);
}
}
try {
if(scope.status === pStatus.pending) {
scope.resovlecbs.push((value)=>{
resolveHandler(value)
})
scope.rejectcbs.push((error)=>{
rejectHanlder(error);
})
} else if(scope.status===pStatus.fulled) {
resolveHandler(scope.value);
} else { // rejectd
rejectHanlder(scope.error);
}
} catch (error) {
reject(error);
}
});
}
export function deepGet(res,Promise2,nextResolve,nextReject){
if(res && res instanceof Promise2) {
if(res.status===pStatus.pending){
res.then(value=>{
deepGet(value,Promise2,nextResolve,nextReject)
},err=>{
nextReject(err)
})
}
} else {
nextResolve(res);
}
}
export function handlerRes(handler,message,nextResolve,nextReject,Promise2){
let res
if(isFunc(handler)){
res = handler(message);
}
deepGet(res,Promise2,nextResolve,nextReject)
}
复制代码
then函数的编写完成了,让咱们再回来看看Promise自己。
Promise是微任务,这里为了方便, 对Promise自己添加宏任务间隔。reject同理。
function Promise(executor:any) {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
this.status = pStatus.pending;
this.resovlecbs = [];
this.rejectcbs = [];
this.value;
this.error;
const resolve = (value:object)=>{
this.value = value;
setTimeout(()=>{
this.resovlecbs.forEach((item:Function)=>{
item(value);
})
this.status = pStatus.fulled;
},0)
}
const reject = (error:Error)=>{
this.error = error;
setTimeout(()=>{
this.status = pStatus.rejected;
if(this.rejectcbs.length ===0){
throw this.error;
} else {
this.rejectcbs.forEach((item:Function)=>{
item(error);
})
}
},0)
// if(this.rejectcbs.length === 0 ) throw error;
}
try {
executor(resolve,reject);
} catch (error) {
reject(error);
}
}
复制代码
然而这依然并不是是最终版本,由于这没法解决,屡次resolve会重复执行 resolvecbs的问题。 因此resolve函数的内容必须旨在pending的状态下才执行。 好比有人会这么作:
new Promise(function(resolve){
reslove('ddd')
resolve('ttt')
}).then(value=>{
console.log(value)
})
复制代码
为了只打印一个值,咱们必需要在resolve函数作个判断,只有pending的时候会
function Promise(executor:any) {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
this.status = pStatus.pending;
this.resovlecbs = [];
this.rejectcbs = [];
this.value;
this.error;
const resolve = (value:object)=>{
setTimeout(()=>{
if(this.status===pStatus.pending){ // 避免重复执行。
this.value = value;
this.resovlecbs.forEach((item:Function)=>{
item(value);
})
this.status = pStatus.fulled; // 状态改变
}
},0)
}
const reject = (error:Error)=>{
setTimeout(()=>{ // why
if(this.status===pStatus.pending){ // 添加了判断 避免重复执行
this.error = error;
this.status = pStatus.rejected; //状态改变
if(this.rejectcbs.length ===0){
throw this.error;
} else {
this.rejectcbs.forEach((item:Function)=>{
item(error);
})
}
}
},0)
// if(this.rejectcbs.length === 0 ) throw error;
}
try {
executor(resolve,reject);
} catch (error) {
reject(error);
}
}
复制代码
基本完美了,然而仍是一个Promise A+标准的处理问题。当resolve(value)的value是个Promise的话,如:
let p1 = new Promise(function(resolve,reject){
resolve('test')
})
new Promise(function(resolve,reject){
resolve(p1)
}).then(value=>{
console.log(value) // 须要打印test。
})
复制代码
目前咱们会直接吧p1这个实例直接返回给then的onFullfilled。继续改造下。
export default function Promise(executor:any) {
if( !isFunc(executor) ){
throw 'Promise2 传递的参数不为functon!!!';
}
this.status = pStatus.pending;
this.resovlecbs = [];
this.rejectcbs = [];
this.value;
this.error;
const resolve = (value:object)=>{
if( value instanceof Promise) { // 这里直接判断
return value['then'](resolve, reject);
}
setTimeout(()=>{
if(this.status===pStatus.pending){
this.value = value;
this.resovlecbs.forEach((item:Function)=>{
item(value);
})
this.status = pStatus.fulled;
}
},0)
}
const reject = (error:Error)=>{
setTimeout(()=>{ //
if(this.status===pStatus.pending){
this.error = error;
this.status = pStatus.rejected;
if(this.rejectcbs.length ===0){
throw this.error;
} else {
this.rejectcbs.forEach((item:Function)=>{
item(error);
})
}
}
},0)
}
try {
executor(resolve,reject);
} catch (error) {
reject(error);
}
}
复制代码
catch函数和finally函数实际上是语法糖,咱们彻底能够用then替代的。读者大大们思考下。。
下面给出代码:
Promise.prototype.catch = function(catchcb:Function) {
return this.then(undefined, catchcb); // 本质是then
}
Promise.prototype.finally = function (callback) {
return this.then((value)=>{ // 本质是then
callback();
return value;
},callback);
}
复制代码
因此下面这种写法
p.then(onResolve,onReject).catch(onCatch).finally(onFinal);
复制代码
实际上是等于
p.then(onResolve,onReject).then(undefined,onCatch).then(onFinal,onFinal);
复制代码
阮一峰给出了这个函数的四种处理方式。
须要注意的是Promise.resolve,传递出来的必定是promise。 笔者的写法是
Promise.resolve = function(handler){
if( isObject(handler) && 'constructor' in handler && handler.constructor=== this) { // handler 是 Promise
return handler;
} else if (isObject(handler) && isFunc(handler.then) ){ // thenable
return new this(handler.then.bind(handler));
} else { // 非thenable
return new this(function(resolve){
resolve(handler);
})
}
}
复制代码
能够看到若是是:
状况1,则直接原封不动的返回。
状况2则返回一个Promise,且把对象的then函数,做为参数传递进入Promise。
状况3 直接把handler resolve掉。
Promise.reject可不像resolve这么麻烦。彻底把传递的值直接传递出来。
Promise.reject = function() {
const args = Array.prototype.slice.call(arguments);
return new this((resolve, reject) => reject(args.shift()));
}
复制代码
首先是用法:
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return new Promise(function(resolve,reject){
setTimeout(()=>{
resolve(id);
},id);
})
});
Promise.all(promises).then(function (posts) {
console.log(posts);
})
复制代码
首先传入的是数组。其次就是全部的数组,其次就是数组存在的Promise所有都执行完后才会进入all的 then函数中。 因此须要一个标记记录实时记录全部已经完成的promise。
而后就是传入的数组 可能有promise也有可能传递的并不是是Promise,因此须要hack。 区分是否存在then函数。
Promise.all = function(arr) {
if( !isArray(arr) ){
throw 'all函数 传递的参数不为Array!!!';
}
let args = Array.prototype.slice.call(arr);
let resArr = Array.call(null,Array(arr.length)).map(()=>null); // 记录全部的结果
let handlerNum = 0; // 处理标记
return new this((resolve,reject)=>{
for(let i = 0;i<args.length;i++){
let ifunc = args[i];
if(ifunc && isFunc(ifunc.then) ) { //是否存在then函数。
ifunc.then(value=>{
resArr[i] = value;
handlerNum ++; // 标记添加
if(handlerNum>=arr.length){ // 完全完成
resolve(resArr) // 完成后的数组
}
},error=>{
reject(error);
});
} else { // 非thenable
resArr[i] = ifunc;
handlerNum ++; // 标记添加
if(handlerNum>=arr.length){ // 完全完成
resolve(resArr) // 完成后的数组
}
}
}
});
}
复制代码
直接上代码吧,大体就是跑的最快的会做为结果传回
Promise2.race = function(arr) {
if( !isArray(arr) ){
throw 'race函数 传递的参数不为Array!!!';
}
let args = Array.prototype.slice.call(arr);
let hasResolve = false;
return new this((resolve,reject)=>{
for(let i = 0;i<args.length;i++){
let ifunc = args[i];
if(ifunc && isFunc(ifunc.then) ) {
ifunc.then(value=>{
!hasResolve && resolve(value)
},error=>{
!hasResolve && reject(error);
});
} else {
hasResolve = true;
!hasResolve && resolve(ifunc)
}
}
})
}
复制代码
源代码大体以下
let test = function() {
return new Promise((resolve,reject)=>{
reject(new Error('test'))
})
}
Promise.resolve('new').then(res=>{
test().then(res2=>{
...
})
}).catch(err=>{
// use err
console.log(err)
})
复制代码
遇到的问题是,最后的catch里面拿不到err。
文中咱们已经说过,catch只是then的语法糖,而then的值的传递,是靠onFullfilled的return 和 onRejected的return 传递了。问题这是在then里面缺乏了return。
resolve作的三件事情。
1)记录传递的值,准备传递。
2)执行then的onFullfilled函数
3)更换状态。
then的执行是根据状态做出不一样的相应的。
catch和finally只是then的语法糖,finally并不是是最后执行的意思,而是必定会执行的意思。
Promise.resolve能够快捷的建立一个Promise,他返回的必定是一个Promise。
想要表达的更清楚点,因此致使内容过长。。。。
有任何问题,请在评论区提问,我会尽快答复。