理解 Promise 基本原理

2022-12-18 0 917

在自学 JavaScript 的操作过程中,触发器是两个很关键的基本上概念,而Promise是一类处置触发器的关键形式。有时候即便他们能娴熟采用Promise,却依然对它很孤单。关键的是认知它的核心理念基本上概念和基本上概念。只但是Promise是两个无法认知的小东西,网路上许多有关它的采用即使是同时实现(即使 Promise是一类规范化国际标准)的该文,或是是间接采用,你一已经开始就得去拒绝接受它,梦境基本上概念、梦境用语,或是是简而言之的「记事本 Promise」,极少有讲透它究竟是甚么的。他们要做的是:介绍它的原初企图、促进作用,自学用语,紧密结合规范化,介绍它的外部同时实现基本上概念,全盘比如说它。

具体来说写作认知 Promises/A+ 规范化。这儿只看呵呵第二段。

第二段:

A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.

两个Promise代表者了两个操作形式今后的结论,常常是应用领域在触发器操作形式上。主要透过then形式,注册登记反弹,转交promise示例的value或是reason。

术语

先单纯认知呵呵下列单字的涵义,基本上都是它原本的涵义,方便快捷前面Promise继续执行操作过程的说明。但是,最后,那些词将再次出现在甚么地方性,它的涵义,互相的亲密关系,你如果极为娴熟!

Promise:允诺executor:开伞器resolve:化解reject:婉拒then:后onFulfilled:被化解的反弹onRejected:被婉拒的反弹value:值reason:其原因PENDING:预备中FULFILLED:已化解REJECTED:已婉拒

采用

两个单纯的 Promise:

// 1 const p1 = new Promise((resolve, reject) => { // 2 ajax({ // 3 url: XXX, // 4 success(data) { // 5 resolve(data) // 6 }, // 7 error (err) { // 8 reject(err) // 9 } // 10 }) // 11 }) // 12 p1.then((value) => { // 13 console.log(value) // 14 }, (reason) => { // 15 console.log(reason) // 16 })

透过以上例子,我们如果或是需要知道的基本上基本上概念(只考虑 Promise 中为触发器代码):

Promise是两个构造函数,采用new Promise创建示例。所以promise是创建出来的两个对象。Promise构造函数有两个参数executor,它是两个函数,在Promise创建示例的操作过程中就会被继续执行。executor函数有两个参数resolve、reject,也都是函数。在executor继续执行的操作过程中,会触发resolve、reject 的继续执行(以上例子中为触发器继续执行)。Promise中有两个属性,value和reason。resolve继续执行时可以传入触发器事件成功反弹继续执行的结论,这个结论改变Promise的 value。resolve继续执行时会继续执行then传入的onFulfilled,参数为Promise的 value。类似,reject继续执行时可以传入触发器事件失败反弹继续执行的结论,这个结论改变Promise的reason。reject继续执行时会继续执行then传入的onRejected,参数为Promise的 reason。Promise示例有then形式,then形式有两个参数,Promise 被化解的反弹 onFulfilled、Promise 被婉拒的反弹 onRejected,都是函数。onFulfilled被传入Promise的onFulfilledCallbacks队列,供resolve继续执行时调用。onRejected被传入Promise的onRejectedCallbacks队列,供reject继续执行时调用。onFulfilled有两个参数value,在实际调用时由resolve函数传递。类似,onRejected有两个参数reason,在实际调用时由reject函数传递。Promise有 3 中状态(state):PENDING(等待中)、FULFILLED(已完成)、REJECTED(已婉拒),用来控制Promise状态,状态不可逆,只能由PENDING到FULFILLED或是PENDING到REJECTED

认知

// TODO: 此处如果有图

以上是根据基本上概念认知操作过程,再用一类通俗的形式认知:整个new Promise表示我允诺(Promise)下列的操作形式,例如两个触发器操作形式,两个 ajax 请求,这是采用者自己的代码,采用者决定甚么时候用化解(resolve)处置曾经的允诺、甚么时候用婉拒(reject)处置曾经的允诺。当你要化解时(比如 ajax 成功时),可以带上要处置的值,比如 ajax 成功后返回的数据,当你要婉拒时,可以带上要婉拒的其原因,比如 ajax 失败后返回的信息。以上的化解(resolve)和婉拒(reject)只是允诺(Promise)要进行的操作形式,他们并不能看到如何处置,具体要进行的操作形式在后(then)进行,但是保证此处会处置。在后(then),采用者要自己编写如何操作形式的细节,处置允诺(Promise)化解时的操作形式(onFulfilled)、允诺(Promise)婉拒时的操作形式(onRejected)。

所以在构建两个允诺的时候,他们允诺一定会化解或者婉拒,但不是在今后去写具体化解形式(反弹函数),而要后(then)就可以立即去写化解的具体操作过程,Promise 给你这样的权利。这是 Promise 要做的事情,透过一类构造(或是说包裹),让你能够在构造后立即处置一些事情,即便那些事情是今后要发生的。是说,你可以放心的在这件事发生的同时,书写今后要操作形式的操作过程,只需要在这件事完成后要进行的操作形式的地方性预留位置,你所写的操作过程就会在那个位置继续执行。这样就化解了触发器代码互相依赖时层层嵌套的地狱反弹问题。

Promise 在 then 中收集反弹函数,在 resolve 中调用的操作过程,实际上是发布订阅模式或是观察者模式。

思考

Promise中是then形式先继续执行,还是创建Promise时resolve()先继续执行?

考虑new Promise中为触发器操作形式:如果是then先继续执行,then将onFulfilled onRejected加入队列,resolve()继续执行时才会有队列形式,采用这个形式才能继续执行then中的形式。

同步调用resolve:

const p1 = new Promise((resolve, reject) => { console.log(1) resolve(value) console.log(2) }) console.log(3) p1.then((value) => { console.log(4, value) }) console.log(5) // 1// 2 // 3 // 5 // 4 value

触发器调用resolve:

const p1 = new Promise((resolve, reject) => { setTimeout(() => { console.log(1) resolve(value) console.log(2) }) console.log(3) }) console.log(4) p1.then((value) => { console.log(5, value) }) console.log(6) // 3 // 4 // 6 // 1 // 2 // 5 value

只但是这个问题考虑复杂了,p1.then()和resolve()谁先继续执行都可以,这是用户写的,怎样写都可以,如果,resolve()先继续执行,这是两个正常的顺序,resolve()在同步中继续执行或是都在触发器中resolve()先继续执行,那么resolve()会改变 Promise的状态,then 的时候触发器调用回调函数。如果resolve()后继续执行,then 已经将反弹函数保存,resolve()继续执行时,触发器调用反弹函数。

始终要搞清楚的是:then 本身不是触发器的,then 的参数、反弹函数 onFulfilled 是触发器的,这导致resolve()是触发器的。

p1.then()这行代码本身是同步继续执行的,resolve()也是。透过上面的例子能看到,Promise要做的是保证resolve里面的操作形式是触发器继续执行的。MDN 中描述:

在本轮 事件循环 运行完成之前,反弹函数是不会被调用的。

在同时实现Promise时:对于同步继续执行resolve时,让onFulfilled(this.value)的继续执行变为触发器(例如setTimeout); 触发器继续执行resolve时,让队列的依次继续执行在触发器中(例如setTimeout);总之,保证onFulfilled()是触发器继续执行的。对应下面Promise同时实现代码中,then 形式中state为FULFILLED时的触发器处置和state为PENDING时对应的 resolve 形式中的触发器处置。

2. 为甚么resolve触发器继续执行时,要采用队列?

const p1 = new Promise((resolve, reject) => { resolve(value) }) p1.then((value) => { console.log(value) }) p1.then((value) => { console.log(value) })

为了应对以上的调用形式,需要队列存储。

总结

Promise只是化解了触发器反弹地狱的问题。Promise经常用来包裹触发器操作形式,后采用then进行调用。then中的反弹函数在Promise resolve或reject后被触发器继续执行。

补充

当然本文没有讨论 then 的链式调用、catch、错误捕获、Promise.all、Promise.race等等问题。仅仅考虑 then 的链式调用,是两个复杂的操作过程。但是如果认知了Promise的基本上基本上概念,在认知触发器和递归的基础上,链式调用也就不难,只但是是返回两个新的Promise,这个Promise处置上两个Promise的结论。

同时实现

下列是 Promise 的一类同时实现:

class MyPromise { static PENDING = PENDING static FULFILLED = FULFILLED static REJECTED = REJECTED constructor(executor) { this.state = MyPromise.PENDING this.value = null this.reason = null this.onFulfilledCallbacks = [] this.onRejectedCallbacks = [] try { executor(this.resolve.bind(this), this.reject.bind(this)) } catch (e) { this.reject.call(this, e) } } resolve(value) { if (this.state === MyPromise.PENDING) { setTimeout(() => { this.state = MyPromise.FULFILLED this.value = value this.onFulfilledCallbacks.forEach((cb) => cb(this.value)) }) } } reject(reason) { if (this.state === MyPromise.PENDING) { setTimeout(() => { this.state = MyPromise.REJECTED this.reason = reason this.onRejectedCallbacks.forEach((cb) => cb(this.reason)) }) } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === function ? onFulfilled : (value) => value onRejected = typeof onRejected === function ? onRejected : (reason) => { throw reason } let promise2 if (this.state === MyPromise.FULFILLED) { return (promise2 = new MyPromise((resolve, reject) => { setTimeout(() => { try { const x = onFulfilled(this.value) this.resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) })) } if (this.state === MyPromise.REJECTED) { return (promise2 = new MyPromise((resolve, reject) => { setTimeout(() => { try { const x = onRejected(this.reason) this.resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) })) } if (this.state === MyPromise.PENDING) { return (promise2 = new MyPromise((resolve, reject) => { this.onFulfilledCallbacks.push((value) => { try { const x = onFulfilled(value) this.resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) this.onRejectedCallbacks.push((reason) => { try { const x = onRejected(reason) this.resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) })) } } resolvePromise(promise2, x, resolve, reject) { let called if (promise2 === x) { reject(new TypeError(Chaining cycle detected for promise)) } if (x instanceof MyPromise) { if (x.state === MyPromise.PENDING) { x.then((y) => { this.resolvePromise(promise2, y, resolve, reject) }, reject) } else { x.then(resolve, reject) } } else if ( (typeof x === object && x !== null) || typeof x === function ) { try { const then = x.then if (typeof then === function) { then.call( x, (y) => { if (called) { return } called = true this.resolvePromise(promise2, y, resolve, reject) }, (r) => { if (called) { return } called = true reject(r) } ) } else { resolve(x) } } catch (e) { if (called) { return } called = true reject(e) } } else { resolve(x) } } } MyPromise.defer = MyPromise.deferred = function () { const defer = {} defer.promise = new MyPromise((resolve, reject) => { defer.resolve = resolve defer.reject = reject }) return defer } module.exports = MyPromise // npm install -g promises-aplus-tests // promises-aplus-tests promise.js

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务