Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript 之 Promise / A+ 规范 #19

Open
kiki-zjq opened this issue Jan 22, 2024 · 0 comments
Open

JavaScript 之 Promise / A+ 规范 #19

kiki-zjq opened this issue Jan 22, 2024 · 0 comments

Comments

@kiki-zjq
Copy link
Owner

JavaScript 之 Promise / A+ 规范

[Promises/A+](https://promisesaplus.com/)

Promise / A+ 规范是一个开放的标准,它详细描述了 Promise 对象的行为,以确保不同的实现可以互相兼容。之所以需要 Promise A+ 规范,主要是因为以下原因:

  1. 一致性:在该规范之前,不同的JavaScript库实现了各自版本的Promise,这些实现在行为上有细微的差异。这种不一致性使得在不同代码库和库版本之间切换成为一项挑战。
  2. 互操作性:规范确保了任何遵循 Promise A+ 的 Promise 库都能够无缝地工作在一起,这对于开发大型、模块化的应用程序来说非常重要。
  3. 明确性:规范清晰地定义了 then 方法的行为,包括值的传播和错误的处理。这消除了开发者的猜测,并减少了代码中的错误。
  4. 提高可维护性:当所有的Promise库都遵循相同的规范时,维护和理解代码变得更加容易,因为开发者知道所有的Promises都遵循同样的基本原则和行为。

总的来说,Promise A+ 规范的存在极大地改善了JavaScript编程环境,使得异步编程更加可靠、一致和可预测。它帮助确立了一个行业标准,让开发者可以放心地使用 Promise,并确信它们会按照预期的方式工作。

1. Terminology

  • promise - is an object or function with a then method whose behavior conforms to this specification.
  • thenable - is an object or function that defines a then method.
  • value - is any legal JavaScript value (including undefined, a thenable, or a promise).
  • exception - is a value that is thrown using the throw statement.
  • reason - is a value that indicates why a promise was rejected.

2. Requirements

2.1 Promise States

promise 的当前状态必须为以下三种状态之一:Pending, Fulfilled , Rejected

  • 处于 Pending 时,promise 可以迁移至 Fulfilled 或 Rejected
  • 处于 Fulfilled 时,promise 必须拥有一个不可变的终值且不能迁移至其他状态
  • 处于 Rejected 时,promise 必须拥有一个不可变的拒绝原因且不能迁移至其他状态

2.2 The then Method

then method accepts two arguments: promise.then(onFulfilled, onRejected)

2.2.1. onFulfilled, onRejected 都为可选参数,如果输入并非一个 Function,则 ignore

2.2.2. onFulfilled 必须等到 promise fulfilled 后再运行,并且以 promise‘s value 作为第一个参数

2.2.3. onRejected 必须等到 promise rejected 后再运行,并且以 promise’s reason 作为第一个参数

2.2.4. onFulfilled or onRejected must not be called until the [execution context](https://es5.github.io/#x10.3) stack contains only platform code.

  • 说白了就是,onFulfilled 和 onRejected 需要是一个异步执行的操作,为了实现这个目的,文档推荐了使用 setTimeout / setImmediate 在下一次 macrotask 中执行。或者使用 MutationObserver / process.nextTick 在本次事件循环的 microtask 中执行。
  • 这条规范规定了 onFulfilled 和 onRejected 服从微任务的执行顺序

2.2.5. onFulfilled and onRejected must be called as functions (i.e. with no this value). (strict mode 下,this = undefined,而在平常模式下,this 指向 global object)

2.2.6. then 方法也许会在同一个 promise 中被调用多次

2.2.7. then must return a promise

promise2 = promise1.then(onFulfilled, onRejected);
  1. If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
  2. If either onFulfilled or onRejected throws an exception epromise2 must be rejected with e as the reason.
  3. If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
  4. If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.

(在这里 3 和 4 描述的其实就是结果透传,无论 promise1 fulfilled 或者 rejected 了,如果 promise2 没法消费这个信息的话,就会把结果传递给下一个 promise)

2.2.7.3 举个例子

	let promise1 = new Promise(function(resolve, reject){
    setTimeout(function(){
      resolve('Promise 1 resolved')
    }, 3000)
  })
  let promise2 = promise1.then(123, function(err) {
    console.log('err: ' + err)
  })
  promise2.then(function(value) {
    console.log('promise2: ' + value)
  })

此时由于 promise1.then() 的第一个参数不是 function,因此 promise2 接收到了 promise1 resolve 得到的值,打印出 promise2: Promise 1 resolved

(resolve 的值被透传了,直到被消费为止)

	let promise1 = new Promise(function(resolve, reject){
    setTimeout(function(){
      resolve('Promise 1 resolved')
    }, 3000)
  })
  let promise2 = promise1.then(function(value) {
    console.log('promise1: ' + value)
		// Here ~~~~
  }, function(err) {
    console.log('err: ' + err)
  })
  promise2.then(function(value) {
    console.log('promise2: ' + value)
  })

此时 promise1 的 resolve 的值被自己的 then 函数的第一个参数函数给消费掉了,最终的打印结果是

promise1: Promise 1 resolved
promise2: undefined

如果在代码 Here~~~ 处 return 一些值,则可以成功传递给 promsie2

2.3 The Promise Resolution Procedure

The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x).

如果 x 是一个 thenable 的对象,那么使 promise 采用 x 此时的状态并且执行

如果 x 不是一个 thenable 的对象,那么 promise 使用 x 作为 value 来完成自己的 promise 流程

	let promise1 = new Promise(function(resolve, reject){
    setTimeout(function(){
      resolve('Promise 1 resolved')
    }, 3000)
  })
  let promise2 = promise1.then(function(value) {
    console.log('promise1: ' + value)
		**return x**
  }, function(err) {
    console.log('err: ' + err)
  })
  promise2.then(function(value) {
    console.log('promise2: ' + value)
  })

例如这个例子中,标红的地方 return 后续的就是 x

如果说 x 是 return 123;

那么在 ES6 中会自动的进行 Promise.resolve(123) 以 123 作为值返回一个状态为 resolved 的 Promise 对象

此时就会打印出 promise2: 123

如果说 x 是 return new Promise(…)

那么就会根据 x 的状态来决定 promise2 的状态

To run [[Resolve]](promise, x), perform the following steps:

2.3.1. 如果 promise 和 x 指向同一个 object,reject promise with a TypeError

注意,promise2 = promise1.then(onFulfilled, onRejected); 会执行 [[Resolve]](promise2, x) 也就是说,当我们在函数里面 return promise2, 就会报错 TypeError: Chaining cycle detected for promise #<Promise>

let promise2 = promise1.then((val) => {
	return promise2
})

2.3.2 如果 x 是一个 promise,则采用它的状态

2.3.2.1 如果 x 是 pending 中,等待 x pending 结束

2.3.2.2 如果 x 是 fulfilled,fulfill promise with same value (也就是说我们此时 resolve(val) 外 promise2 也会使用 val 进行自己的 resolve)

2.3.2.3 如果 x 是 rejected,fulfill promise with same err

2.3.3 如果 x 是一个对象或者函数

2.3.3.1 如果 x 中包含 then 方法那么就执行这个 then 方法,call it with x as this, first argument resolvePromise, and second argument rejectPromise

  • 如果 resolvePromise called with a value y → run [[Resolve]](promise, y). 因此会有一个迭代解析发生
  • 如果 rejectPromise called with a reason err → reject promise with err
  • 如果 resolvePromiserejectPromise 都被调用了或者被调用了多次,那么只管第一个出现的,后续出现的不要管!
  • 如果发生了 err → 如果已经触发了 resolvePromise 或者 rejectPromise ,那么忽视这个 err。否则的话,reject promise with err as the reason

2.3.3.2 如果 x 就是一个纯纯的对象或者函数,而没有 then,或者 then 不是一个 function。那么就 fulfill promise with x

2.3.4 如果 x 不是一个对象或者函数

fulfill promise with x.


实现 Promise

xieranmaya/blog#3

调用

const p = new Promise(function(onResolve, onReject) {
	...
	...
	onResolve(value)
})

基本框架

function Promise(executor) { // executor 也就是我们 new Promise(fn) 的那个 fn
  var self = this
  self.status = 'pending' // Promise当前的状态
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
  self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面

  function resolve(value) {
    // TODO
  }

  function reject(reason) {
    // TODO
  }

  try { // 考虑到执行executor的过程中有可能出错,所以我们用try/catch块给包起来,并且在出错后以catch到的值reject掉这个Promise
    executor(resolve, reject) // 执行executor
  } catch(e) {
    reject(e)
  }
}

注意这个地方 onResolvedCallback 怎么样才能往这个数组里面 push function 呢?

通过 then 方法,但是有一个细节需要注意!

let promise = new Promise.resolve(111)
promise.then(val => console.log(val))
promise.then(val => console.log(val))

例如这里的方式,就成功往这个数组里面push了回调函数。并且会连续打印两个 111

也就是说,同一个 value 会被用于 onResolvedCallback 里面的所有函数

let promise = 
	new Promise.resolve(111)
		.then(val => console.log(val))
		.then(val => console.log(val))

然而这种方式只往数组里面push了一个 callback function。并且打印结果是

111undefined 。 这是因为 then 方法返回的是一个新的 promise,因此第二个 then 方法实际上是 push 到了这个新的 promise 的 onResolvedCallback 中。

考虑 resolvereject

function Promise(executor) {
  // ...

  function resolve(value) {
    if (self.status === 'pending') {
      self.status = 'resolved'
      self.data = value
      for(var i = 0; i < self.onResolvedCallback.length; i++) {
        self.onResolvedCallback[i](value)
      }
    }
  }

  function reject(reason) {
    if (self.status === 'pending') {
      self.status = 'rejected'
      self.data = reason
      for(var i = 0; i < self.onRejectedCallback.length; i++) {
        self.onRejectedCallback[i](reason)
      }
    }
  }

  // ...
}

then 方法

var x = onResolved(self.data) 这一部分后续的内容可以结合前面提到的 Promise Resolve Procedure 规范思考

Promise.prototype.then = function(onResolved, onRejected) {
  var self = this
  var promise2

  // 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
  onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
  onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}

  if (self.status === 'resolved') {
    // 如果promise1(此处即为this/self)的状态已经确定并且是resolved,我们调用onResolved
    // 因为考虑到有可能throw,所以我们将其包在try/catch块里
    return promise2 = new Promise(function(resolve, reject) {
      try {
        **var x = onResolved(self.data)**
        if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
          x.then(resolve, reject)
        }
        resolve(x) // 否则,以它的返回值做为promise2的结果
      } catch (e) {
        reject(e) // 如果出错,以捕获到的错误做为promise2的结果
      }
    })
  }

  // 此处与前一个if块的逻辑几乎相同,区别在于所调用的是onRejected函数,就不再做过多解释
  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
      try {
        var x = onRejected(self.data)
        if (x instanceof Promise) {
          x.then(resolve, reject)
        }
      } catch (e) {
        reject(e)
      }
    })
  }

  if (self.status === 'pending') {
  // 如果当前的Promise还处于pending状态,我们并不能确定调用onResolved还是onRejected,
  // 只能等到Promise的状态确定后,才能确实如何处理。
  // 所以我们需要把我们的**两种情况**的处理逻辑做为callback放入promise1(此处即this/self)的回调数组里
  // 逻辑本身跟第一个if块内的几乎一致,此处不做过多解释
    return promise2 = new Promise(function(resolve, reject) {
      self.onResolvedCallback.push(function(value) {
        try {
          var x = onResolved(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })

      self.onRejectedCallback.push(function(reason) {
        try {
          var x = onRejected(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}

// 为了下文方便,我们顺便实现一个catch方法
Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}

一些其它问题

Promise 穿透

new Promise(resolve=>resolve(8))
  .then()
  .then()
  .then(function foo(value) {
    alert(value)
  })

希望 alert(8)

我们只需要把then里判断onResolvedonRejected的部分改成如下即可:

onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason}

完整代码

try {
  module.exports = Promise
} catch (e) {}

function Promise(executor) {
  var self = this

  self.status = 'pending'
  self.onResolvedCallback = []
  self.onRejectedCallback = []

  function resolve(value) {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(function() { // 异步执行所有的回调函数
      if (self.status === 'pending') {
        self.status = 'resolved'
        self.data = value
        for (var i = 0; i < self.onResolvedCallback.length; i++) {
          self.onResolvedCallback[i](value)
        }
      }
    })
  }

  function reject(reason) {
    setTimeout(function() { // 异步执行所有的回调函数
      if (self.status === 'pending') {
        self.status = 'rejected'
        self.data = reason
        for (var i = 0; i < self.onRejectedCallback.length; i++) {
          self.onRejectedCallback[i](reason)
        }
      }
    })
  }

  try {
    executor(resolve, reject)
  } catch (reason) {
    reject(reason)
  }
}

/*
resolvePromise函数即为根据x的值来决定promise2的状态的函数
也即标准中的[Promise Resolution Procedure](https://promisesaplus.com/#point-47)
x为`promise2 = promise1.then(onResolved, onRejected)`里`onResolved/onRejected`的返回值
`resolve`和`reject`实际上是`promise2`的`executor`的两个实参,因为很难挂在其它的地方,所以一并传进来。
相信各位一定可以对照标准把标准转换成代码,这里就只标出代码在标准中对应的位置,只在必要的地方做一些解释
*/
function resolvePromise(promise2, x, resolve, reject) {
  var then
  var thenCalledOrThrow = false

  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise!'))
  }

  if (x instanceof Promise) {
    if (x.status === 'pending') { //because x could resolved by a Promise Object
      x.then(function(v) {
        resolvePromise(promise2, v, resolve, reject)
      }, reject)
    } else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
      x.then(resolve, reject)
    }
    return
  }

  if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
    try {
      then = x.then //because x.then could be a getter
      if (typeof then === 'function') {
        then.call(x, function rs(y) {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          return resolvePromise(promise2, y, resolve, reject)
        }, function rj(r) {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          return reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (thenCalledOrThrow) return
      thenCalledOrThrow = true
      return reject(e)
    }
  } else {
    resolve(x)
  }
}

Promise.prototype.then = function(onResolved, onRejected) {
  var self = this
  var promise2
  onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
    return v
  }
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
    throw r
  }

  if (self.status === 'resolved') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() { // 异步执行onResolved
        try {
          var x = onResolved(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (reason) {
          reject(reason)
        }
      })
    })
  }

  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() { // 异步执行onRejected
        try {
          var x = onRejected(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (reason) {
          reject(reason)
        }
      })
    })
  }

  if (self.status === 'pending') {
    // 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义
    return promise2 = new Promise(function(resolve, reject) {
      self.onResolvedCallback.push(function(value) {
        try {
          var x = onResolved(value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (r) {
          reject(r)
        }
      })

      self.onRejectedCallback.push(function(reason) {
          try {
            var x = onRejected(reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (r) {
            reject(r)
          }
        })
    })
  }
}

Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}

Promise.deferred = Promise.defer = function() {
  var dfd = {}
  dfd.promise = new Promise(function(resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

最简实现Promise,支持异步链式调用(20行)

[最简实现Promise,支持异步链式调用(20行) - 掘金](https://juejin.cn/post/6844904094079926286)

function Promise(fn) {
  this.cbs = [];

  const resolve = (value) => {
    setTimeout(() => {
      this.data = value;
      this.cbs.forEach((cb) => cb(value));
    });
  }

  fn(resolve);
}

Promise.prototype.then = function (onResolved) {
	// 这里 return 的是 promise2
  return new Promise((resolve) => {
		// this 指的是 promise1,这里是往 promise1 的 cbs push callback
    this.cbs.push(() => {
      const res = onResolved(this.data); // 执行 then 的第一个入参 并且得到返回值
      if (res instanceof Promise) {
				// 如果返回的是一个 Promise 对象 (称作 user promise)
				// 将 Promise2 的 resolve的权力交给了 user promise
				// user promise 调用 resolve 会使得 promise2 的状态变成 fulfilled
        res.then(resolve);
      } else {
				// 如果是普通值 就直接resolve
        // 依次执行cbs里的函数 并且把值传递给cbs
        resolve(res);
      }
    });
  });
};

一道有趣的面试题

Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve(4);
}).then((res) => {
    console.log(res)
})

Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() =>{
    console.log(6);
})

// 大家先思考一下

最后这里再贡献一道有趣的面试题,最终输出的结果竟然是 0 1 2 3 4 5 6

关键问题在于 return Promise.resolve(4) ,这句话实际上包含一个非常复杂的流程

首先,Promise 内部会试图解析 return x 的这个 x(这里 x 是 Promise.resolve(4)),这一步是一个微任务

然后,Promise 解析成功后要执行 Promise.resolve(4) ,这一步也是一个微任务

随后才是执行 .then(res => console.log(res))

因此整段下来,微任务队列入队情况类似于

0 -> 1 -> PromiseResolve(Promise.resolve(4)) -> 2 -> Promise.resolve(4) -> 3 -> console.log(res) -> 5 -> 6

也可以理解成

Promise.resolve().then(() => {
    console.log(0);
}).then(() => {
    // 微任务 PromiseResolve(Promise.resolve(4))
}).then(() => {
	 // 微任务 Promise.resolve(4)
		return 4
})
.then(res => console.log(res))

Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() =>{
    console.log(6);
})

// 大家先思考一下
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant