基于浏览器内建Promise深入理解Promise/A+规范的细节

javascript/jquery

浏览数:44

2020-5-26

AD:资源代下载服务

本文主要记录Promise/A+规范中那些不太被关注的细节,展现规范中所描述的具体场景。

预热

  • 思考以下场景下的执行结果。
Promise.reject(10010).then().then(function(value){
  console.log('onFulfilled value: ',value);
}, function (reason) {
  console.log('onRejected reason: ',reason)
})
Promise.resolve().then(function () {
  var obj = {
    name:'outer',
    then:{name:'inner'},
  }
  return obj;
}).then(function(value){
  console.log('onFulfilled value: ',value);
},function (reason) {
  console.log('onRejected reason: ', reason)
})
Promise.resolve().then(function () {
  var obj = {
    then(resolvePromise,rejectPromise) {
      resolvePromise(Promise.resolve(10010))
    }
  }
  return obj;
}).then(function(value){
  console.log('onFulfilled value: ',value); 
},function (reason) {
  console.log('onRejected reason: ', reason)
})
Promise.resolve().then(function () {
  var obj = {
    then(resolvePromise,rejectPromise) {
      rejectPromise(Promise.resolve(10010))
    }
  }
  return obj;
}).then(function(value){
  console.log('onFulfilled value: ',value);
},function (reason) {
  console.log('onRejected reason: ', reason)
})
var resolveAtGlobal;
var p = new Promise(function(resolve){
  resolveAtGlobal = resolve;
});
p.then(function(value){
  console.log('onFulfilled value: ', value);
},function(reason){
  console.log('onRejected reason: ', reason);
});
resolveAtGlobal(p);

规范解读

  • 2.2.2 如果 onFulfilled 是一个函数
  • 2.2.2.1 promise 是 fulfilled 状态时它必须被调用,并且 promise 的 value 作为它的第一个参数。
Promise.resolve().then(function(value){
  console.log('onFulfilled value: ',value);
  console.log(arguments.length)
  console.log(arguments)
});
  • 2.2.3 如果 onRejected 是一个函数
  • 2.2.3.1 promise 是 rejected 状态时它必须被调用,并且 promise 的 reason 作为它的第一个参数。
Promise.reject().then(function(value){
  console.log('onFulfilled value: ',value);
},function(){
  console.log('onRejected');
  console.log(arguments.length)
  console.log(arguments)
});
  • onFulfilled和onRejected被调用时一定有value/reason(默认使用undefined)。
  • 2.2.5 onFulfilled 和 onRejected 必须以函数的形式被调用(即没有this值)。[3.2]
Promise.resolve().then(function(){
  console.log(this);
});
Promise.resolve().then(function(){
  'use strict';
  console.log(this);
})
  • onFulfilled 和 onRejected被调用时,如果是严格模式执行函数,函数内部的this就指向undefined,否则指向顶层对象。
  • 2.2.7 then 必须返回一个promise [3.3]
  • 2.2.7.1 如果 onFulfilled 或 onRejected 返回一个值 x,运行Promise决议程序 [[Resolve]](promise2, x)。
Promise.resolve(10010).then(function () {
  return Promise.resolve(10086)
}).then(function(value){
  console.log('onFulfilled value: ',value)// 10086
}, function (reason) {
  console.log('onRejected reason: ',reason);
});
  • 2.2.7.2 如果 onFulfilled 或 onRejected 抛出一个意外 e,promise2 必须以 e 为 reason 被 rejected。
Promise.resolve().then(function () {
  throw -1;
}).then(function(value){
   console.log('onFulfilled value: ',value)
}, function (reason) {
  console.log('onRejected reason: ',reason)// -1
})
Promise.reject().then(function(){},function () {
  var a = null;
  a.num = 1;
}).then(function(){}, function (reason) {
  console.log('onRejected reason: ',reason) // TypeError
})
  • 2.2.1.1 如果 onFulfilled 不是一个函数,它必须被忽略。
  • 2.2.1.2 如果 onRejected 不是一个函数,它必须被忽略。
  • 2.2.7.3 如果 onFulfilled 不是一个函数并且 promise1 处于 fulfilled 状态,promise2 必须以与 promise1 同样的 value转变到 fulfilled 状态。
  • 2.2.7.4 如果 onRejected 不是一个函数并且 promise1 处于 rejected状态,promise2 必须以与 promise1 同样的 reason转变到 rejected状态。
Promise.reject(10010).then().then(function(){}, function (reason) {
  console.log('onRejected reason: ',reason)// 10010
})
Promise.resolve(10086).then().then(function (value) {
  console.log('onFulfilled value: ',value)// 10086
})

2.3 Promise决议程序

promise决议程序是一个抽象的操作,它把一个 promise 和一个 value 作为输入,我们将这个表示为 [[Resolve]](promise, x)。如果 x 是一个 thenable ,它将会试图让 promise 采用 x 的状态,前提是x的行为至少有点像一个 promise。否则,它将会用值 x 执行 promise。

对这些 thenable 的处理使得与 promise 实现方式能够去互相操作。只要它们公开了符合 Promise/A+ 的 then 方法。它还使得 promises/A+ 实现方式能够采用合理的 then 方法去“同化”不一致的实现方式。

为了运行[[Resolve]](promise, x),执行以下步骤:

2.3.1 如果 promise 与 x 是同一个对象,以 Tyeperror 作为 reason 去 reject promise。

var resolveAtGlobal;
var p = new Promise(function(resolve){
  resolveAtGlobal = resolve;
});
p.then(function(value){
  console.log('onFulfilled value: ',value);
},function(reason){
  console.log('onRejected reason: ', reason) // error:循环决议链
});
resolveAtGlobal(p);
var resolveAtGlobal;
var p = new Promise(function(resolve){
  resolveAtGlobal = resolve;
}).then(function(value){
  console.log('onFulfilled value: ',value);
  return p;
},function(reason){
  console.log('onRejected reason: ', reason)
});
p.then(function(value){
  console.log('onFulfilled2 value: ',value);
},function(reason){
  console.log('onRejected2 reason: ', reason) // error:循环决议链
});
resolveAtGlobal();
  • 2.3.3 否则,如果 x 是一个对象或者函数:
  • 2.3.3.1 让 then 作为 x.then。
  • 2.3.3.2 如果取属性 x.then 会导致抛出异常 e,则以 e 为 reason reject promise。
var proxy = new Proxy({}, {
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}!`);
    throw -1;
  },
});
Promise.resolve(1).then(function(){
  return proxy;
}).then(function (value) {
  console.log('onFulfilled value: ',value);
}, function (reason) {
  console.log('onRejected reason: ', reason) 
});
  • 2.3.3.3 如果 then 是一个函数,让 x 作为 this 调用它,第一个参数为 resolvePromise,第二个参数为 rejectPromise,然后:
Promise.resolve().then(function () {
var obj = {
  then() {
    console.log('this === obj :', this === obj);
  }
}
return obj;
});
  • 2.3.3.3.1 如果使用value y 调用 resolvepromise 时,运行[[Resolve]](promise, y)。
Promise.resolve().then(function () {
  var obj = {
    then(resolvePromise,rejectPromise) {
      resolvePromise(Promise.resolve(10010))
    }
  }
  return obj;
}).then(function(value){
  console.log('onFulfilled value: ',value); // 10010
})
  • 2.3.3.3.2 如果使用reason r 调用 rejectPromise 时,也用 r reject promise。
Promise.resolve().then(function () {
  var obj = {
    then(resolvePromise,rejectPromise) {
      rejectPromise(Promise.resolve(10010))
    }
  }
  return obj;
}).then(function(value){
  console.log('onFulfilled value: ',value);
},function (reason) {
  console.log('onRejected reason: ', reason) // promise
})
  • 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都被调用了,或多次调用同一参数,那么第一个调用优先,其他的调用都会被忽略。
Promise.resolve().then(function () {
  var obj = {
    then(resolvePromise,rejectPromise) {
      resolvePromise(10010);
      rejectPromise(10000);
      resolvePromise(10086);
    }
  }
  return obj;
}).then(function(value){
  console.log('onFulfilled value: ',value); // 10010
},function (reason) {
  console.log('onRejected reason: ', reason)
})
  • 2.3.3.3.4 如果调用 then 的过程中抛出了一个意外 e,
  • 2.3.3.3.4.1 如果 resolvePromise 或者 rejectPromise 被调用了,那么忽略它。
  • 2.3.3.3.4.2 否则,把 e 作为 reason reject promise。
Promise.resolve().then(function () {
  var obj = {
    then(resolvePromise,rejectPromise) {
      resolvePromise(10010);
      throw -1
    }
  }
  return obj;
}).then(function(value){
  console.log('onFulfilled value: ',value); // 10010
},function (reason) {
  console.log('onRejected reason: ', reason)
})
Promise.resolve().then(function () {
  var obj = {
    then(resolvePromise,rejectPromise) {
      rejectPromise(10010);
      throw -1
    }
  }
  return obj;
}).then(function(value){
  console.log('onFulfilled value: ',value); 
},function (reason) {
  console.log('onRejected reason: ', reason)// 10010
})
Promise.resolve().then(function () {
  var obj = {
    then(resolvePromise,rejectPromise) {
      throw -1
    }
  }
  return obj;
}).then(function(value){
  console.log('onFulfilled value: ',value); 
},function (reason) {
  console.log('onRejected reason: ', reason)// -1
})
  • 如果 then 不是一个函数,将 x 作为参数执行 promise。
Promise.resolve().then(function () {
  var obj = {
    name:'outer',
    then:{name:'inner'},
  }
  return obj;
}).then(function(value){
  console.log('onFulfilled value: ',value); // value.name === 'outer'
},function (reason) {
  console.log('onRejected reason: ', reason)
})
  • 2.3.4 如果 x 不是一个对象或者函数,将 x 作为参数执行 promise。
  • 如果一个参与了 thenable 循环链的 thenable 去 resolve promise,这样 [[Resolve]](promise, thenable) 的递归性质最终会导致 [[Resolve]](promise, thenable) 会被再次调用,遵循上述算法将会导致无限递归。我们鼓励去实现(但不是必需的)检测这样的递归,并以 TypeError 作为 reason 去 reject Promise。[3.6]
// 使用chrome和火狐测试,并没有实现递归检测。浏览器当前标签页会失去响应, timeoutHandle也不会执行
Promise.resolve().then(function () {
  var obj = {
    then(resolvePromise, rejectPromise) {
      resolvePromise(obj)
    }
  }
  return obj;
}).then(function (value) {
  console.log('onFulfilled value: ', value);
}, function (reason) {
  console.log('onRejected reason: ', reason)
});

setTimeout(function timeoutHandle() {
  console.log('timeout') // timer
}, 0);
  • 3.注意
  • 3.3 如果实现满足所有要求,则实现可能允许 promise2 == promise1。每个实现都应该记录它是否能够生成 promise2 == promise1 以及在什么条件下。
  • 似乎没有哪个Promise库在完成Promise/A+规范的同时又实现了 promise2 == promise1。🤔
  • 3.6 实现方式中不应当在 thenbale 链中的深度设置主观的限制,并且不应当假设链的深度超过主观的限制后会是无限的。只有真正的循环才能导致 TypeError。如果遇到由无限多个不同 thenable 组成的链,那么永远递归是正确的行为。
  • 有没有一种可靠的方式能实现对thenbale链是否真正循环做出检测呢。🤔
Promise.resolve().then(function () {
  var obj = {
    then(resolvePromise, rejectPromise) {
      if(Math.random()> 1e-10){
        resolvePromise(obj)
      }else{
        resolvePromise('done');
      }
    }
  }
  return obj;
}).then(function (value) {
  console.log('onFulfilled value: ', value);
}, function (reason) {
  console.log('onRejected reason: ', reason)
});

setTimeout(function timeoutHandle() {
  console.log('timeout') // timer
}, 0);
  • 这里thenbale链可能是无限循环的,也可能不是,这是个概率问题.

参考资料

promisesaplus.com


promise/A+规范翻译以及手写实现

作者:pass10n