Knex: 如何配置使用标准的 ES6 Promise?

创建于 2016-07-22  ·  80评论  ·  资料来源: knex/knex

使用 node v6 运行时,有没有办法配置为仅使用标准 ES6 Promise(摆脱蓝鸟依赖)?

例如在 promise-queue 包中,它默认使用全局可用的 Promise,

https://www.npmjs.com/package/promise-queue

或者用户可以通过Queue.configure(require('vow').Promise);显式配置它

那么这个包可以实现类似的策略吗?

discussion

最有用的评论

:+1: 用于原生 Promise。

所有80条评论

好奇:您想用较慢的内置库替换较快的库吗? 在客户端上没关系,但节点速度是一个因素。 你这样做的理由是什么?

@johanneslumpe在某些应用程序中这是一个因素, knex我非常怀疑使用的Promise库对性能有任何显着影响。 已经讨论过我们应该编写所有Promise代码以仅使用 A+ API,这样就不需要bluebird

之后应该很容易覆盖使用哪个 Promise 库。

我同意在 knex 之类的库中感觉毫无意义。 鉴于 knex 的代码库已经在内部导入了它自己的promise.js文件,它在技术上仍然很容易实现,只要 API 是相同的_(我不确定它是吗?)_。 在这个文件中,可以默认为全局承诺,否则需要配置的库,或类似的东西。

这一切都是为了给用户一个选择; 对于 Node V6+ 用户,请选择尽可能少地管理 3rd 方依赖项。

好奇:您想用较慢的内置库替换较快的库吗?

你所说的过去可能是真的,但现在或 6 个月后呢? (我做了一些搜索,只能找到一些 2015 年的基准,如果你有更新的比较,请发布一些链接)
我相信让 Promise 成为 ES6 标准的全部目的是让人们可以轻松使用 Promise,而不必依赖 3rd 方库,并且 Node 或 V8 的核心团队不能永远对性能差异视而不见,只要两个项目的开源许可兼容,甚至可以借用一些代码; 或者只是给他们一些时间,我相信内置的 Promise 可以更快更好。

参见亚马逊的 AWS-SDK:它也默认使用全球可用的任何 Promise; 同时让用户可以选择配置最喜欢的 Promise 库

http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/node-making-requests.html#Support_for_Promises

一切都是关于选择

再想一想,这并不像更改单个文件那么容易。 Knex 目前在很大程度上依赖于 Bluebird 实用程序函数,例如.timeout.tap.reduce.map等等,我假设并期望不存在在 ES6 承诺中。

我对支持 ES6 承诺很感兴趣。 理想情况下,我们需要Knex构造函数的第二个参数,它采用Promise构造函数。 可以像这样实现向后兼容性:

const Promise = require('bluebird');
const knex = Knex(myKnexConfig, Promise);

也许我们可以根据提供的Promise.prototype上的存在有条件地别名mapfilter等?

我认为这在优先级列表中应该很低,并且需要进行相当多的内部更改,更不用说性能下降(尽管有一段时间没有看到基准测试)以及人们可能依赖的事实事实上,返回的承诺是一只蓝鸟(对于有条件的catch等)。

我更倾向于等到 async/await 进入一个稳定的节点来解决这个问题。

对于想要最少 3rd 方依赖的人来说,原生 Promise 会更好,

@google-cloud和许多其他库一样,您可以将其设置为默认使用本机 Promise 并接受想要使用 3rd 方库的承诺参数

https://googlecloudplatform.github.io/google-cloud-node/#/docs/google-cloud/

var gcloud = require('google-cloud')({
  promise: require('bluebird')
});

@tgriesser我知道这是一个老问题,但我们现在在稳定版和 Carbon 中都有async / await

鉴于 async/await 首选使用本机承诺(根据规范, async函数必须返回本机承诺),这是更高的优先级吗?

如果本地承诺支持是 Knex 希望发生的事情,但目前还没有出现在雷达上,那么 PR 会受到欢迎吗?

谢谢你的时间。

@malexdev async函数确实返回本机承诺,目前它与 knex 没有太大关系。 这并不意味着await需要原生 Promise 才能正常工作。 您能否澄清在哪种情况下这是一个问题/好处(除了删除一个依赖项)?

话虽如此,我并不反对放弃 bluebird,但它确实需要进行相当多的内部更改。 我们可能需要实现一些当前从bluebird向 knex API 公开的方法,除非旧功能隐藏在某些配置开关下,并且默认情况下将使用本机承诺。 对于那些一直在传递 knex 返回bluebird承诺这一事实的人来说,这种方式迁移并非不可能。

@elhigu对我个人而言,主要的好处是我使用 TypeScript 和 TSLint 规则,强制执行用于await的本机承诺,因此我必须将所有 Knex 调用包装在Promise.resolve()中。 我意识到这与 Knex 没有任何关系,并且可能是我独有的问题。

除此之外,在我看来,更少的 3rd 方依赖更好,正如@c0b提到的那样,更多的选择从来都不是一件坏事。

我意识到这将是大量的工作,这也是我非常乐意花时间在这方面的原因之一,如果这是 Knex 感兴趣的事情。

是的,我是从打字稿问题来到这里的——我使用 Knex 作为我的多存储数据存储库的 SQL 引擎,虽然我对原生 vs bluebird 的承诺感到矛盾,但我不能轻易使用出于这个原因,在 knex 上打字。 我将 knex thenables 视为遵循本机规范(我不使用任何蓝鸟扩展),但打字稿让我对返回蓝鸟感到不满当方法声明是 Promise 时.

这里有两个层次,因为我们正在处理 Promise 实现和 knex 的类型(由不同的开发人员处理),但我基本上被困在这里 - 我在技术上打破了类型合同当我声明一个 Promise 时返回一个 Bluebird(我猜 Promise api 中有一些 Bluebird 不支持的东西?)但我真的不想在任何地方都放一堆return Promise.resolve(bluebirdthing)

在过去的一年里,我已经花了足够的时间在 knex 的胆量中挖掘,并与一般的承诺一起工作,如果人们愿意,我愿意在这里做一些事情并致力于 PR 以模块化 Promise 实现 - 你会对 PR 开放? 它最终会像@elhigu提到的那样 - 重新实现一些实用程序函数以使用在实例化时传入的任何 Promise 构造函数,以避免代码重写需求。 当然,不确定性能,但这是可以进行基准测试的。

用 async / await 完成这一切也很酷,而且我并不急于解决这个问题(最终对于我的用例,我只是最终将事情标记为any并处理这些代码分支,就好像它们是 javascript 而不是打字稿一样)。

@ericeslinger我不明白为什么正在使用的真正的 Promise 实现会导致 TypeScript 出现问题,一年半以来我一直在混合蓝鸟和原生承诺,没有任何问题......

我只是没有使用任何会为 bluebird 引入类型的类型,我只是告诉 typescript 类型它们是正常的 Promises 并且它没有看到任何区别(如果我尝试使用 bluebird 的特殊方法,它会抱怨)。

哪个打字稿版本以及是否有任何示例项目可以重现...例如带有 npm start 脚本的 github repo。

这里有两个层次,因为我们正在处理 Promise 实现和 knex 的类型(由不同的开发人员处理),但我基本上被困在这里 - 我在技术上打破了类型合同当我声明一个 Promise 时返回一个 Bluebird(我猜 Promise api 中有 Bluebird 不支持的东西?)但我真的不想在任何地方都放一堆 return Promise.resolve(bluebirdthing) 。

那些来自 npm 的 knex 类型? 是来自 npm 的蓝鸟类型吗? 哪些包? 我很难理解为什么会发生这种情况。 如果有单独的 Bluebird 类型,它应该从原生 Promise 继承,并且可以从告诉它们将返回 Promise 的 API 返回。 从描述看来,问题在于打字实现非常糟糕。 这与这个问题无关(打字稿不介意真实类型,所以它不会知道 knex 返回什么)。

我通过安装@types/knexDefinitelyTyped repo 获取我的输入。 该定义与它一起使用@types/bluebird ,并且所有 knex 方法都被键入为返回 Bluebird 对象。

作为一个具体的事情,我不能这样做:

function mungeData(v: any): DataItem {} 
function foo(): Promise<DataItem[]> {
  return knex('data').select()
  .then((rows) => rows.map(row => mungeData(row)))
} // error, cannot return Bluebird<DataItem[]> as Promise<DataItem[]>

使用这些类型。 这是因为 knex.select().then() 被键入以在 DefinitiveTyped 存储库中返回蓝鸟,并且这些链在一起以制造更多蓝鸟,并且当 foo 是蓝鸟时说类似return foo as Promise<any>()将失败(至少它在 typescript 2.4 中失败),因为 Bluebirds 不能分配给 Promises(根据this ,它们缺少 [Symbol.toStringTag] ,因此强制一个到另一个将是错误的,尽管是一个小错误)。

相反,我可以更改为

function bar(): Promise<DataItem[]> {
  return Promise.resolve<DataItem[]>(foo())
}

或使用其他技巧将所有对 knex 的调用包装在本机 Promise.resolve() 调用中。 这将导致打字稿停止抱怨我的库函数的下游,同时仍然允许我在我的库函数中使用 knex 类型。

在昨天之前,我根本没有使用@types/knex - 我只是将 knex 输入为any 。 代码在运行时无论哪种方式都可以正常工作(至少对于我的用例),它只是

@elhigu :问题不在于打字实现。
TypeScript 将async函数的类型设置为Promise<[type]> ,这在 JS 规范中是正确的。
Knex 返回Bluebird<[type]> ,打字准确地反映了这一点。

我只是没有使用任何会为 bluebird 引入类型的类型,我只是告诉 typescript 类型它们是正常的 Promise,它没有看到任何区别

这是对编译器的欺骗,因为 Knex 函数实际上返回Bluebird s。 没兴趣。
Bluebirds 与 Promises 兼容是正确的,但与 TypeScript 的部分交易是您实际上返回了您所说的返回内容。

当从已键入返回Promise Bluebird函数返回 Bluebird 时,TypeScript 会抱怨,因为类型Bluebird与类型Promise不同。
我们可以做各种技巧(例如@ericeslinger提到的关于使用any或包装Promise.resolve()的技巧),但归根结底,这样的技巧会让我们失去很多TypeScript 提供了什么。

归根结底,现实情况是现在至少有两个用户在说“使用原生 Promise 对我们很重要,我们愿意投入工作让 Promise 功能更加通用”。

我意识到你只是想帮忙,但坦率地说,我不想听到“你可以这样做”,我想听听我/@ ericeslinger / @c0b提出的承诺更改是否可以接受,以便我可以开始在公关什么的。

@malexdev @ericeslinger感谢您提供更多信息! 看起来实际上不可能从Promise继承您自己的类,因此这可能是从类型为 Promise<> 的函数返回 Bluebirds 失败的原因:(

@ericeslinger无论如何,当您创建async函数时,这不是问题,因为它们会自动将结果包装到内部的原生 Promise 中。 以下没有问题,使用来自@types/bluebird 的类型并编译为 ES2015 或 ESNEXT。

import * as Bluebird from 'bluebird';

// declaring function async converts bluebird implicitly to native Promise
async function asyncReturningPromise(): Promise<string> {
    const blueBirdPromise = new Bluebird<string>((resolve, reject) => { 
        resolve('yay asyncReturningPromise');    
    });
    return blueBirdPromise;
}

// main func to run the code using async / await
Bluebird.resolve().then(async () => {
    console.log("await function returning promise (bluebird)", await asyncReturningPromise());

    const blueBird = new Bluebird((resolve, reject) => { resolve(); });
    const returnedFromAsync = asyncReturningPromise();

    console.log("Bluebird instanceof Promise:", blueBird instanceof Promise);
    console.log("async retval instanceof Promise:", returnedFromAsync instanceof Promise);
});

输出:

await function returning promise (bluebird) yay asyncReturningPromise
Bluebird instanceof Promise: false
async retval instanceof Promise: true

因此,现在当您使用 knex API 时,您实际上需要告诉您正在返回 Bluebird,除非您使用的是异步函数/方法,它会自动将 bluebird 包装到本机 Promises。

@malexdev

这是对编译器的欺骗,因为 Knex 函数确实返回 Bluebirds。 没兴趣。
Bluebirds 与 Promises 兼容是正确的,但与 TypeScript 的部分交易是您实际上返回了您所说的返回内容。

实际上处理打字稿是返回对象正确实现接口就足够了,例如这很好:

class FakePromise<T> implements Promise<T>  {
    [Symbol.toStringTag]: "Promise";
    then<TResult1, TResult2>(onfulfilled?: (value: T) => TResult1 | PromiseLike<TResult1> | null | undefined, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2> | null | undefined): Promise<TResult1 | TResult2> {
        return new Promise((resolve, reject) => { resolve('Im totally broken and fake!); });
    }
    catch<TResult>(onrejected?: (reason: any) => TResult | PromiseLike<TResult> | null | undefined): Promise<T | TResult> {
        throw new Error("Method not implemented.");
    }
}

// this works and  fake promise instance has nothing to do with native promise
function returningPromiseInterface(): Promise<string> {
    const fakePromise = new FakePromise<string>();
    return fakePromise;
}

// compiling this fails, because looks like Bluebird actually doesn't implement Promise interface correctly
function asyncReturningPromise(): Promise<string> {
    const blueBirdPromise = new Bluebird<string>((resolve, reject) => { 
        resolve('yay asyncReturningPromise');    
    });
    return blueBirdPromise;
}

它搞砸了 instanceof,但 typescript 实际上甚至没有承诺您返回本机 Promise 实例,仅返回接口。

当从已键入返回 Promise 的函数返回 Bluebird 时,TypeScript 会抱怨,因为类型 Bluebird 与类型 Promise 不同。
我们可以使用各种技巧(例如@ericeslinger提到的使用 any 或包装在 Promise.resolve() 中的技巧),但归根结底,这样的技巧会让我们失去 TypeScript 提供的大部分功能。

我不想看到人们不得不做那种技巧/改变 JS 实现只是为了满足糟糕的打字。

归根结底,现实情况是现在至少有两个用户在说“使用原生 Promise 对我们很重要,我们愿意投入工作让 Promise 功能更加通用”。

我意识到你只是想帮忙,但坦率地说,我不想听到“你可以这样做”,我想听听我/@ ericeslinger / @c0b提出的承诺更改是否可以接受,以便我可以开始在公关什么的。

感谢您的理解 :) 更改 knex 以使用本机 Promise 已在去年开始并实施到某个时间点,然后由@tgriesser更改回来,所以我想说现在最好不要开始此更改。

此外,我仍然认为这个线程中提到的这些打字稿问题是类型声明中的问题(为什么 bluebird 没有正确实现 Promise ......我需要更深入地研究它?),而不是 knex 实现中的问题。

话虽如此,我并不反对在某个时间跨度内摆脱蓝鸟,只是在这里看到两个不同的问题。

:+1: 用于原生 Promise。

@elhigu

实际上处理打字稿是返回对象正确实现接口就足够了

很公平。 我仍然坚持我的观点,即更少的依赖和更多的选择更好,但我现在明白了你所说的错误类型是什么意思。

所以还是 👍 对于原生 Promises(我仍然愿意提供帮助),但我现在看到我的直接问题可以通过修复 Bluebird 类型来解决。 谢谢(你的)信息。

所以我开始大量使用 TypeScript,我都很喜欢它,现在意识到这里的问题是一个真正的痛点。 随着 async/await 在最近的 Node 领域获得更多立足点,Bluebird 实用程序 fns ( map , reduce , tap , bind , return ) 变得不那么有用了。 我会很高兴继续在内部使用 Bluebird,但正式“弃用”返回所有其他实用程序方法的 knex 查询构建器的公共 api。

合并后,我们可以升级 TypeScript 定义以删除 Bluebird 类型( toBluebird除外),并将 Bluebird 类型更改为 Promise 类型。

如果有人有带宽想要解决这个问题,这就是我正在考虑的行动计划:

  • [ ] 为所有 Bluebird 代理方法( tapmapreducebindreturn )添加弃用警告。
  • [ ] 添加一个.toBluebird()方法,这将是那些想要继续使用 Bluebird 的人的迁移路径(他们可以通过简单地查找/替换上述方法的所有调用并在之前添加它来实现那些被称为)
  • [ ] 一旦合并/新版本削减(0.15),我们可以更新 Typescript 定义
  • [ ] 最终我们可以完全放弃这些方法。 这个更简单的 API 为最终使用原生 Promises 和 async/await 铺平了道路。

让我知道这是否有意义以及是否有人对此感兴趣。

肯定会对此有所帮助。

这是否意味着 Knex 将开始维护自己的 TypeScript 定义? 将允许我们使用自动生成的类型永远不会支持的泛型做一些很酷的事情。

我第一次尝试添加对原生 Promises 的支持,开始了这个 fork:
https://github.com/tgriesser/knex/pull/2523/files

喜欢 2016 年的评论:

好奇:您想用较慢的内置库替换较快的库

惊人的在 2 年内可以改变多少。

当从已键入返回 Promise 的函数返回 Bluebird 时,TypeScript 会抱怨,因为类型 Bluebird 与类型 Promise 不同。

@malexdev实际上打字稿使用结构类型(流程使用名义类型并且会按照您描述的方式工作),所以只要您的类型满足Promise接口,它就与Promise兼容,无论它是否明确extends / implements它与否。

这进展如何? 我认为一个好的第一步是在 knex 中排除 Bluebird 特定的方法调用(即实际上还没有删除它)。 随后将移除 bluebird 并为自定义 Promise 构造函数提供选项(并为使用 Bluebird 方法的人们提供升级路径)。

如果没有异议,我不会从第一步开始工作。 现有的工作似乎已经消亡。

@qubyte我认为没有积极的努力来进行更改,这里和那里进行了增量更改,但仅此而已。

好的。 在我的下一段空闲时间中,我将进行一些尽可能小的更改来分解每种方法。

@tgriesser我们什么时候应该继续这个(如果有的话)有什么意见吗? 对我来说,明年 4 月 Node 6 LTS 到达生产线末端时听起来是一个合理的时间。

2018年有趣的信息:

promises-native-async-await promises-bluebird
参考: https ://github.com/petkaantonov/bluebird/tree/master/benchmark

因此,性能不再是保留 bluebird 的理由。 我们应该去异步/等待。

promises-native-async-await 性能更好

这也是我在 2016 年坚信的,原生方式会改进得更快,因为它是 Nodejs 社区的核心,有更多的人关心它,比任何第三方库都多

虽然提交的票是要求选择的,但有很多竞争的Promise实现,永远赌蓝鸟是不好的

有这方面的更新吗?

@cfanoulis同样仍然存在。 4 月到来时,我们可以放弃对 Node 6 的支持并开始移除 bluebird。

2019有什么更新吗? /cc 给一些核心贡献者或维护者或任何关心它的人@here@johanneslumpe @tgriesser @wubzz @elhigu来自https://github.com/tgriesser/knex/graphs/contributors?type=c&from=2018-01- 01&to=2019-12-31

另一方面,JavaScript 社区是一个充满活力、充满活力,有时甚至是残酷的世界,每 3 或 2 年(甚至更快)就会有我们以前熟悉的东西的替代品,想想 Grunt,Gulp => Webpack,这些工具,库,框架在各个层面都在充分竞争,所以,对于旧库,如果你停止引入创新,或者放慢对新标准的支持速度(想想 ES2019 异步/等待迭代器......),你最终将被取代

我只是做了一些简单的研究,看起来在 DB ORM 级别也有很多替代方案,TypeORM 可能是一个不错的选择......(我在这里不再多说......)
https://bestofjs.org/tags/db
https://bestofjs.org/projects/typeorm

@c0b无需抄送。 无论如何,我都会收到来自所有评论的邮件。 @kibertoad刚刚在他的最后一条评论中说了所有必须说的话......这个问题也与 knex 无关关于。

如果你需要一个好的 ORM,我可以推荐 objection.js。 它也是在 knex 之上实现的,这是关于它的非常好的线程https://github.com/Vincit/objection.js/issues/1069

在某些时候,这个 knex 将被替换,但不会被任何 ORM 替换。 它可以被其他一些具有更简洁的代码库和更一致的 API 的查询构建器所取代。 例如 knex 1.0 可能;)

此外,如果更换 knex,我会完全接受,对我来说工作更少:D

我相信有 WIP: https ://github.com/tgriesser/knex-next

还想提一下,在使用async_hooks时不使用本机承诺会导致https://github.com/nodejs/node/issues/22360导致当前上下文丢失。

相信我,我们不需要额外的理由来搬家,我们想像你们一样做坏事:)。 但是,我们仍然需要对 Node 6 分支发布更多修复,然后(最终)我们将放弃它并开始逐步淘汰 bluebird。

#3227合并后,我们终于可以开始了!

我知道您之前提到过您可以在此迁移中使用帮助,如果这仍然是您想要的方向,我们可以提供任何帮助吗?

我在想:做一个项目,添加几个任务,看看是否有人(也许我有时间)可以被分配并设置一些日期?

@chaffeqa将很快创建一些更精细的任务,让#3250 进行第一轮简单的更改。 主要是我们需要用基于原生的东西来替换 bluebird.tap、bluebird.method 和 bluebird.try 的用法。 如果您已经有一些时间,您可以尝试分支 #3250 并查看任何剩余的“蓝鸟”需求(我建议从非方言特定的需求开始,以便您可以通过运行test:sqlite快速验证仍然有效的功能

@qubyte如果你想贡献,现在是时候了!

@kibertoad我现在可以安全使用 async/await 吗?

你的意思是在 knex 代码库中? 当然。 在你自己的,你一直是:-D

很抱歉上周成为 MIA,我们公司的事情正在加速发展,所以我必须专注于那里。

想要结束我们关于升级的更大块之一的一些讨论的循环:替换Disposer用法

当你开始研究它时,它是一个非常深的问题,所以需要一些好的工程来提供一个好的副本/抽象。 我担心某些东西的性能开销可能会很大(随着承诺链的增长,会创建更多的对象)。

我实际上是从几个 POC 开始的,我认为这是其中最直接的:

class DisposablePromise extends Promise {

  disposerFunc = null;
  originalResource = null;

  then(onFulfilled, onRejected) {
    const $onFulfilled = this.wrap(onFulfilled);
    return super.then($onFulfilled, onRejected).copyContext(this);
  }

  copyContext(promise) {
    this.disposerFunc = promise.disposerFunc;
    this.originalResource = promise.originalResource;
    return this;
  }

  disposer(disposerFunc) {
    this.disposerFunc = disposerFunc
  }

  isDisposable() {
    return !!this.disposerFunc
  }

  wrap(onFulfilled: any) {
    const $onFulfilled = (result: any) => {
      if (this.disposerFunc && !this.originalResource) {
        this.originalResource = result
      }
      if (result instanceof Promise) {
        return onFulfilled(result);
      } else {
        const res = onFulfilled(result)
        if (this.disposerFunc) {
          this.disposerFunc(this.originalResource)
        }
        return res
      }
    };

    return $onFulfilled;
  }
}

还有一个:

      var DisposablePromise = function DisposablePromise() {
          var self = DisposablePromise.convert(Promise.resolve());
          return self;
      };
      DisposablePromise.convert = function convert(promise, props) {
          promise.__proto__ = DisposablePromise.prototype;
          return props ? Object.assign(promise, props) : promise;
      };
      DisposablePromise.prototype = Object.create(Promise.prototype);
      DisposablePromise.prototype.constructor = DisposablePromise;
      DisposablePromise.prototype.then = function then(resolve, reject) {
          var returnVal = Promise.prototype.then.call(this, resolve, reject);
          return DisposablePromise.convert(returnVal);
      };
      DisposablePromise.prototype.catch = function _catch(err) {
          var returnVal = Promise.prototype.catch.call(this, err);
          return DisposablePromise.convert(returnVal);
      };
      DisposablePromise.prototype.finally = function finall(obj) {
          var returnVal = Promise.prototype.finally.call(this, obj);
          return DisposablePromise.convert(returnVal);
      };
      DisposablePromise.prototype.disposer = function disposer(disposerFunc) {
        var returnVal = Promise.prototype.finally.call(this, obj);
        return DisposablePromise.convert(returnVal);
      };

但还没有时间证明他们。

我认为实际探索其他选项可能是值得的(保留 bluebird 但将其转换为在内部使用本机 Promise?),因为此功能必须在存储库中(除非您能想到更好的方法......异步迭代器?很想听听蓝鸟团队对抽象该功能的任何想法,尽管我的直觉说它与蓝鸟实现挂钩非常相关。

我想说,如果我们能弄清楚这部分,剩下的任务就很简单了。

@chaffeqa Np,感谢您仍然有时间回到这个问题!
我非常怀疑蓝鸟人会愿意接受建议以认真地重新设计他们的实现,他们一再重申这一点,在这一点上他们对稳定性最感兴趣,并且他们确实建议人们实际使用原生 Promise,除非真的需要高级功能由蓝鸟提供。
考虑到 Node 8 似乎是目前最受欢迎的 Node.js 版本(基于官方 Node.js 下载统计数据),恐怕我们还不能转向基于异步迭代器的方法。
您认为 Knex 在内部实施 DisposablePromise 有什么缺点? 由于它扩展了原生 Promise,我认为它不会带来 Bluebird 的任何缺点,并且用户空间不需要知道它?

@ericeslinger FWIW,TS 类型在 master 中应该不再是问题了,我们现在将我们的承诺作为本地承诺,以阻止用户依赖 Bluebird 功能。 当原生 Promises 实现 Bluebird 承诺没有的东西时,这可能会导致问题,所以我们仍然希望尽可能地替换使用的 Promise。 任何沿着这些方向的贡献将不胜感激:)

坚果我想的一样多😞
我同意在这种情况下做类似DisposablePromise的方法可能是要走的路,特别是因为真正需要的项目仍在提案中

不利的一面是,以明智的方式设计像DisposablePromise这样的东西非常重要......坦率地说,我什至不知道我的实现是否有效😆(由于某种原因,我在思考异步时遇到了很多麻烦哈)。

如果此线程上还有其他人想在此问题上进行尝试<3 u longtimes!

@chaffeqa Bluebird 的实现有多复杂? 也许我们可以简单地提取它并添加到原生承诺之上?

@chaffeqa最坏的情况 - 如果我们认为触摸风险太大,我们可以删除所有其他 Bluebird 用法并保留它,因为它的复杂性。 不理想,但最终会发生using

不幸的是相当复杂......实现依赖于蓝鸟控制承诺生命周期的事实。 我认为最好的方法是查看它正在尝试做什么(这与上面using上的链接非常接近)并为其创建尽可能简单和高性能的 shim。

问题是管道必须Bluebird样式的承诺,如果我理解正确的话,它不符合本机承诺的性能(因此您失去了所有跟踪 + 本机异步功能)。

我宁愿做一些在后台使用原生承诺的异步部分,但提供绑定上下文和实现所需用法的能力,如disposer

仅供参考,我想到的另一件事是:在 knex 中实际上很少使用usage.disposer ,所以也许这种方法更适合将其提升到更高的水平?

值得一试:)

oooo 也是我根据https://github.com/petkaantonov/bluebird/issues/1593找到的一个选项

无论哪种方式,我认为向前迈出的一大步是您在上一个分支上开始的,我们隔离了所有实际上是BluebirdPromisePromise使用,这样我们就可以开始玩 drop在DisposablePromiseBluebirdNativePromise等替代品中。

@chaffeqa您的意思是Bluebird.setScheduler(fn => Promise.resolve().then(fn))部分?
整体转换进行得非常顺利! 如果我们可以在 Bluebird 中保留 Disposers,同时让它们在底层使用原生 Promise,那实际上可能是一个很好的解决方案。

还想提一下,在使用async_hooks时不使用本机承诺会导致nodejs/node#22360导致当前上下文丢失。

解决方法是使用https://github.com/TimBeyer/cls-bluebird补丁。

仅供参考,Node v8 的 LTS 将于今年结束。

@Bessonov上下文? 将最小节点增加到 10 对这个问题有何影响? 请注意,我们已经放弃了对节点 6 的支持。

我不熟悉 knex 代码库,但也许有一些功能可以帮助您摆脱蓝鸟。 例如,节点 10 支持Promise.finally

但无论如何,我很高兴看到这个主题的进展:+1:

关于 disposer 模式 - 我们可以只为返回一次性承诺的事物添加可选回调吗?
(就像交易一样)

getDisposableConnection(config, cb) {
    const connection = await getConnection(config)

   // user want autodisposable connection
    if (cb) 
      Promise.resolve(cb(connection)).then(() => connection.dispose())
   // user will dispose by himself
   return connection
}

我们需要哪个级别的承诺库独立性?
1) 全部使用原生承诺
2) internals native promises,用户可以为接口设置自己的promise lib
3)用户可以为内部和接口设置promise lib

这个问题的现状是什么。 通常 knex 现在可以与 async await 一起使用,但 typescript 会报告警告我们正在等待不是本机承诺的方法。

所以回答原始问题的问题。 当前的解决方法是简单地等待并添加类似// tslint:disable-next-line: await-promise的内容

@maximelkin我投票支持选项 1。从长远来看,我希望每个 Promise 库都会过时。

其次,在这一点上,即使对于大多数浏览器,我们也超出了 Promise polyfills

@Bessonov目前在 knex 依赖库(可能还有项目)上,这需要完全蓝鸟

我们应该为他们提供一些后备解决方案

@Bessonov目前在 knex 依赖库(可能还有项目)上,这完全需要蓝鸟,我们应该为它们提供一些后备解决方案

knex 的用户是否依赖 bluebird 并不重要。 Knex 仍然可以使用原生 Promise,并且它们可以与 bluebird Promise 很好地互操作。 我们绝对不应该给予任何后备。

所以这个问题从选择承诺实现的功能请求开始。
不知从何而来,它突变为无缘无故地移除蓝鸟,并打破所有家属。 没有任何警告、更新日志、回退选项和主要版本。

但我想所有 1.5 typescript 用户现在都很开心。

所以这个问题从选择承诺实现的功能请求开始。
不知从何而来,它突变为无缘无故地移除蓝鸟,并打破所有家属。 没有任何警告、更新日志、回退选项和主要版本。

至少更早的 knex 0.x 版本被认为是具有潜在破坏性更改的主要版本,因此只有更新到 0.20.x 才应该被认为是安全升级(当版本号 < 1 时,semver 真的很松散)。

移除蓝鸟已经被摆在桌面上很久了,不仅仅是这个问题。

无缘无故移除蓝鸟

移除蓝鸟并不是无缘无故的。 你仍然可以在外部使用 bluebird 和 knex promise。 放弃 bluebird 的一个重要原因是async函数隐式地创建了原生 Promise,因此将来继续使用 Bluebird 将需要在 knex API 中添加额外的 bluebird 包装代码,而没有任何理由。

没有任何警告,更新日志,

同意。 我翻阅了最新的变更日志……遗憾的是,看起来我们确实未能列出版本之间的重大变更。 我们在编写变更日志时需要更加小心,以真正指出变更,这会破坏旧的 API。 例如,许多类型的更改实际上会破坏旧的 TS 代码。

ioredis 项目https://github.com/luin/ioredis/commit/da60b8b 也有同样的问题。 他们想要支持原生 Promise - 并且人们提出了一个非常好的解决方案 - 他们添加了一个选项来支持任何自定义 Promise 库,并且他们默认使用原生 Promise。 为什么不? 设置自定义 Promise 库速度很快,并且不需要修补所有应用程序代码。

继续使用 Bluebird 将需要在 knex API 中添加额外的 bluebird 包装代码,这无缘无故。

对。 但是,如果明确指定了模块调用,为什么不在 bluebird(或任何其他 Promise 库)中包装模块调用呢? 这是一个简单的包装器,零开销,它允许用户使用他们想要的任何承诺库。 如果没有人需要 bluebird,那么没有人会使用此选项,您可以及时安全地弃用它。

另外,我看到一个意见,

从长远来看,我希望每个 Promise 库都会过时。

但是恕我直言,有两个错误的假设:

  • 使用 Bluebird 是因为它更快。
  • 蓝鸟被用作花粉填充物。

我认为对于真正复杂的应用程序而言,情况并非如此,它超越了 async-await one liner。
Bluebird 有许多复杂的异步流绝对必要的特性——比如超时、自定义错误处理、并发映射、取消、减少等等。 所有这些特性都可以在原生 Promise 中实现,但这是很多无用的样板。 2020 年,我们仍在 Node 12 中使用 bluebird,因为我们不想要所有这些样板文件。

为什么不? 设置自定义 Promise 库速度很快,并且不需要修补所有应用程序代码。

任何在内部使用 async-await 的东西都会将 Promise 强制转换为原生 Promise,因此您的选择是要么将每个方法的输出包装在自定义 Promise 中,要么在内部代码中禁止 async-await。 这并不像第一次检查时看起来那么小。

@qubyte

这并不像第一次检查时看起来那么小。

不,就像我已经说过的那样简单。 您为导出的外部函数制作了一个包装器,仅此而已。 大约 10 行代码。 并以您想要的任何方式编写所有内部代码。

@jehy :如果您看到实现它们的简单方法,请随时为这 10 行代码提交 PR。

我今天还将花一些时间尝试解决问题。

值得一提的是,bluebird 的大部分 API 都通过这些包使用本机承诺与相同或接近的 API 重复: https ://github.com/sindresorhus/promise-fun

值得一提的是,bluebird 的大部分 API 都使用这些包的原生承诺与相同或接近的 API 重复

约 50 个包裹而不是 1 个? 严重地?

是的,尽管大多数时候只需要少数几个(例如 p-map)。 当然,您的里程可能会有所不同。 它仅作为您想要的一种潜在途径提供。

@jehy :您可以在应用程序代码中尝试以下临时解决方法:

const Bluebird = require('bluebird');


const prototypesNeedingDecoration = [
  require('knex/lib/query/builder').prototype,
  require('knex/lib/schema/builder').prototype,
  require('knex/lib/transaction').prototype,
  require('knex/lib/raw').prototype,
];

const corePromiseMethods = ["then", "catch", "finally"];


function decoratePromiseMethods(target) {
  for(const m of corePromiseMethods) {
    const original = target[m];

    target[m] = function(...args) {
      return Bluebird.resolve(original.apply(this, args))
    }
  }  
}

function hackBluebird() {
  for(const target of prototypesNeedingDecoration) {
    decoratePromiseMethods(target);
  }
}


hackBluebird();

这并不是对整体问题的真正充分解决方案。 在knex中创建的其他临时对象需要以类似的方式进行装饰。

此外,免责声明:解决方法☝️几乎没有经过测试。 因此,您应该重新运行应用程序的测试以确保没有任何问题。

只是想在这里加上我的 2 美分:我真的很感谢在这次迁移中所做的所有工作,不管负面反馈如何。

从我们的应用程序的角度来看,knex 是最后一个迫使我们需要 Bluebird 的库,并且符合完整的原生承诺支持意味着:

  1. 我们不再有污染的堆栈跟踪
  2. 我们将 SSR 的重量减轻了很多
  3. 我们改进了性能,因为本机 async await 现在比 bluebird 性能更高(并且越来越多!)

继续向 es 标准迈进是一个巨大的胜利……我知道这对图书馆维护者来说并不容易,所以我想向你们大喊一声,感谢你们承担了这样的负担!

对于那些遭受变化的人:我很乐意提供帮助,因为我们已经受益,所以如果您需要帮助调试或迁移,请联系!

@chaffeqa感谢您的反馈,这意味着很多!

@jehy :您有没有机会尝试解决建议的问题? 如果是这样,它是否解决了您的直接问题?

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

nklhrstv picture nklhrstv  ·  3评论

legomind picture legomind  ·  3评论

tjwebb picture tjwebb  ·  3评论

rarkins picture rarkins  ·  3评论

saurabhghewari picture saurabhghewari  ·  3评论