在 JavaScript ES6 中,thenable 的接受如何解决和拒绝?

Posted

技术标签:

【中文标题】在 JavaScript ES6 中,thenable 的接受如何解决和拒绝?【英文标题】:In JavaScript ES6, how can a thenable accept resolve and reject? 【发布时间】:2019-12-26 19:47:02 【问题描述】:

我认为到目前为止我采取的一个原则是:

promise 是一个 thenable 对象,因此它接受消息then,或者换句话说,一些代码可以调用这个对象上的then 方法,它是接口的一部分,带有一个实现处理程序,这是“下一步要采取的步骤”,以及拒绝处理程序,这是“如果没有成功,下一步要采取的措施”。通常最好在fulfillment handler中返回一个新的promise,这样其他代码就可以“链”上它,也就是说,“我也会告诉你下一步的动作,如果你失败了下一步的动作,所以当你完成后调用其中一个。”

然而,在a javascript.info Promise blog page 上,它说实现处理程序可以返回任何“thenable”对象(这意味着类似 promise 的对象),但是这个 thenable 对象的接口是

.then(resolve, reject) 

这与通常的代码不同,因为如果一个履行处理程序返回一个新的承诺,这个 thenable 对象有接口

.then(fulfillmentHandler, rejectionHandler)

所以该页面上的代码实际上得到了resolve 并调用resolve(someValue)。如果fulfillmentHandler 不只是resolve 的另一个名字,那么为什么这个thenable 不一样呢?

该页面上的thenable代码:

class Thenable 
  constructor(num) 
    this.num = num;
  
  then(resolve, reject) 
    alert(resolve); // function()  native code 
    // resolve with this.num*2 after the 1 second
    setTimeout(() => resolve(this.num * 2), 1000); // (**)
  


new Promise(resolve => resolve(1))
  .then(result => 
    return new Thenable(result); // (*)
  )
  .then(alert); // shows 2 after 1000ms

【问题讨论】:

这只是使用了一对令人困惑的名称。含义没有什么不同,then 的实现对于现实世界的 thenable 来说并不是一个好主意(它不像一个承诺)。 另请注意,在原生 JavaScript 中无法测试接口。因此,只要一个对象具有 then 方法,它就会作为 thenable 传递。该方法甚至不必声明参数。它可以为所欲为。这是一个thenable。 另请注意,在 Promise 上调用 resolve 不会直接调用 fullfillmentHandler 我们的意思是then(fulfillmentHandler, rejectionHandler)then(resolve, reject) 是同一个东西吗?所以fulfillmentHandler实际上是resolve? (或resolveHandler?)我把它当作new Promise(function(f, g) ... ,JS引擎立即调用这个执行器并传入一些本机代码来解析fresolve......我们从不使用@调用then(f, g) 987654344@ 是resolve 是的。您也可以附加多个fullfilmentHandlers,但承诺resolves 只能附加一次。 【参考方案1】:

let p2 = p1.then( onfulfilled, onrejected)

其中p1 是一个Promise 对象,对p1then 调用返回一个promise p2 并在p1 内部保存的列表中记录4 个值:

    onfulfilled的值, 创建p2时传递给执行程序的resolve函数的值——我们称之为resolve2onrejected的值, 在创建p2 时传递给执行程序的reject 函数的值——我们称之为reject2

1.3. 具有默认值,因此如果省略,它们会将满足的 p1 值传递给 p2,或使用分别为p1的拒绝原因。

2.4.p2 的解析和拒绝函数)在内部保存,用户 JavaScript 无法访问。

现在让我们假设p1 已(或已经)通过调用resolve 函数以非thenable 值传递给其执行程序来完成。本机代码现在将搜索 p1 的现有 onfulfilled 处理程序,或处理添加的新处理程序:

每个onfulfilled 处理程序(上面的1)都从try/catch 块内的本机代码执行,并监控其返回值。如果返回值(称为v1)是不可调用的,则以v1 作为参数调用p2resolve,并沿链继续处理。

如果 onfulfilled 处理程序抛出,p2 被拒绝(通过调用上面的 4)并抛出错误值。

如果onfulfilled 处理程序返回一个thenable(promise 或promise 类对象),我们称它为pObjectpObject 需要设置,将其稳定状态和值传递给上面的p2

这是通过调用来实现的

pObject.then( resolve2, reject2)

因此,如果pObject 满足,则其非thenable 成功值用于解析p2,如果它拒绝,则其拒绝值用于拒绝p2

博客文章使用参数名称定义其 thenable 的 then 方法,该方法基于它在博客示例中的使用方式(解决或拒绝先前通过本机承诺调用 then 返回的承诺)。原生 promise resolvereject 函数是用原生代码编写的,这解释了第一条警报消息。

【讨论】:

不错的分析。所以我认为是的,因为 promise 系统也是用 JS 编写的,我们可以推断出在某些时候,pObject.then( resolve2, reject2) 必须被调用,因此可以利用这个事实吗?在某种程度上,这就像挖掘黑匣子,总结出它的一些属性并加以利用。我认为在这种情况下这是有道理的。如果某些人认为抽象(黑盒)不是您应该猜测的东西,那么可能不推荐这种做法。 如果一种编程语言内置了 Promise 并且是由一些关键字或语法完成的,那么发生的事情就可以通过“魔术”发生。 then(resolve, reject) 的使用增加了一些混乱,因为它不是通常的接口:resolve, reject 提供给传递给 promise 构造函数的函数,而不是提供给 then()。但我想下次我们看到then(resolve, reject) 时,我们可以考虑有人将resolveHandler 作为fulfillmentHandler 传递,以解决某些问题。 @nopole 在引入 ECMAScript 2015(又名 ES6)之前,所有的 Promise 都是用 JavaScript 编写的,通常作为更大库的一部分。其中一些库仍在使用中。当然,所有旧浏览器的 Promise pollyfills 也是用 JavaScript 编写的。原生 Promise 实现很可能是用原生代码编写的。 Promise 的一个主要设计目标是对用户隐藏 Promise 逻辑的实现。我从了解 Promise 的实际工作原理中受益匪浅,但它们的明确设计是为了防止从用户代码中改变其设计行为。 @nopole Techniclly,在 2019 年 ECMAScript 标准中,使用 Promise 解决 Promise 达到了将 PromiseResolveThenableJob 排队。根据链接的第 2 步和以下注释,调用 thenable 的 then 方法来解析或拒绝 p2。我认为这可能是博客命名参数的运行“PromiseResolveThenableJob”的性质。另请参阅 A+ promises 规范以了解更早的承诺解决方案。【参考方案2】:

写下整个解释后,简短的回答是:这是因为 JS promise 系统传入了 resolvereject 作为 fulfillmentHandlerrejectionHandler。在这种情况下,所需的fulfillmentHandlerresolve

当我们有代码时

new Promise(function(resolve, reject)  
  // ...
).then(function() 
  return new Promise(function(resolve, reject)  
    // ...
  );
).then(function()  ...

我们可以写同样的逻辑,使用

let p1 = new Promise(function(resolve, reject)  
  // ...
);

let p2 = p1.then(function() 
  let pLittle = new Promise(function(resolve, reject)  
    // ...
    resolve(vLittle);
  );
  return pLittle;
);

返回pLittle 的行为意味着:我正在返回一个promise,一个thenable,pLittle。现在,一旦接收者收到此 pLittle,请确保当我将 pLittle 解析为值 vLittle 时,您的目标是立即将 p2 也解析为 vLittle,以便可链接操作可以继续。

它是怎么做到的?

它可能有一些类似的代码:

pLittle.then(function(vLittle)          // ** Our goal **
  // somehow the code can get p2Resolve
  p2Resolve(vLittle);
);

上面的代码说:当pLittlevLittle解析时,下一个动作是用相同的值解析p2

所以不知何故系统可以得到p2Resolve,但是在系统内部或者“黑盒”里面,上面的函数

            function(vLittle) 
  // somehow the code can get p2Resolve
  p2Resolve(vLittle);

可能是p2Resolve(这主要是一个猜测,因为它解释了为什么一切正常)。所以系统会

pLittle.then(p2Resolve);

记住

pLittle.then(fn) 

意思

pLittle的解析值传递给fn并调用fn,所以

pLittle.then(p2Resolve);

相同
pLittle.then(function(vLittle) 
  p2Resolve(vLittle)
);

和上面的** Our goal**完全一样。

它的意思是,系统传入一个“resolve”,作为一个履行处理程序。所以在这个确切的时刻,履行处理程序和解析是同一件事

请注意,在原始问题的 Thenable 代码中,确实如此

return new Thenable(result);

这个Thenable 不是一个promise,你不能解决它,但是由于它不是一个promise 对象,这意味着promise(如p2)作为返回的规则被立即解决,这就是为什么会立即调用then(p2Resolve)

所以我认为这取决于这样一个事实,即 ES6 Promise 的内部实现将 p2Resolve 作为第一个参数传递给 then(),这就是为什么我们可以实现任何带有第一个参数 resolve 的 thenable 和只需调用resolve(v)

我认为 ES6 规范很多时候都写出了确切的实现,所以我们可能会以某种方式使用它。如果任何 JavaScript 引擎的工作方式略有不同,那么结果可能会发生变化。我想在过去,我们被告知我们不应该知道黑盒内部发生了什么,也不应该指望它是如何工作的——我们应该只知道接口。所以最好还是不要返回一个带有接口then(resolve, reject)的thenable,而是返回一个使用接口then(fulfillmentHandler, rejectionHandler)的新的、真实的promise对象。

【讨论】:

If any JavaScript engine works slightly differently, then the results can change 不。那么它不是一个有效的 ES262 引擎。 “所以最好还是不要返回一个有接口的thenable”,当然你可以这样做。 感觉有点hacky,但似乎可以与其他框架一起使用,有时您可以使用这样的thenable 它一点也不hacky。就是这样。 所以也许在 Promise 社区中,这是链式 Promise 的标准方式,人们认为这是理所当然的,你能说。

以上是关于在 JavaScript ES6 中,thenable 的接受如何解决和拒绝?的主要内容,如果未能解决你的问题,请参考以下文章

javascript ES6 - 在函数中传播运算符

在 ES6 深度嵌套的对象的 javascript 数组中查找值

dict 在 python 3 中传播(ES6 javascript like , ... notation)

如何在 Javascript/Lodash/ES6 中同时搜索父对象和子对象?

JavaScript ES6 - 函数扩展

JavaScript ES6 - 函数扩展