创建(ES6)承诺而不开始解决它


77

使用ES6承诺,如何在不定义解决承诺的逻辑的情况下创建承诺?这是一个基本示例(某些TypeScript):

var promises = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return promises[key];
  }
  var promise = new Promise(resolve => {
    // But I don't want to try resolving anything here :(
  });

  promises[key] = promise;
  return promise;
}

function resolveWith(key: string, value: any): void {
  promises[key].resolve(value); // Not valid :(
}

使用其他promise库很容易做到。jQuery的例子:

var deferreds = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return deferreds[key].promise();
  }
  var def = $.Deferred();    
  deferreds[key] = def;
  return def.promise();
}

function resolveWith(key: string, value: any): void {
  deferreds[key].resolve(value);
}

我能看到的唯一方法是将resolve函数存储在Promise的执行程序中某个地方,但这似乎很麻烦,而且我不确定确切地在运行此函数时定义了它-它总是在构造时立即运行吗?

谢谢。


1
你会做什么?没有解决逻辑的承诺是永远待定的承诺。
Bergi 2015年


1
@Bergi-想象一下像异步依赖注入系统这样的东西。您有一部分用于注册注射项目,而另一部分则用于请求注射项目。如果我请求尚未注册的项目,那么我将希望返回一个承诺,该承诺一旦解决便会解决。
Barguast 2015年

Answers:


91

好问题!

传递给promise构造函数的解析器有意地同步运行以支持此用例:

var deferreds = [];
var p = new Promise(function(resolve, reject){
    deferreds.push({resolve: resolve, reject: reject});
});

然后,在以后的某个时间点:

 deferreds[0].resolve("Hello"); // resolve the promise with "Hello"

给出promise构造函数的原因是:

  • 通常(但并非总是)解析逻辑与创建绑定在一起。
  • promise构造函数是安全抛出的,并将异常转换为拒绝。

有时,它不适合,因此解析器会同步运行。这是有关该主题的相关阅读


我一直在思考Promise.resolve并存储界限then。但是,这看起来很干净,我甚至不确定绑定then是否会起作用。
thefourtheye

哇。感谢您如此迅速而彻底的回答!您是否碰巧有一个源指出执行程序(带有resolve和reject参数的函数)在构造时始终同步运行?
Barguast 2015年

1
@Barguast可以肯定,尤其是对于ES6-ecma-international.org/ecma-262/6.0/…是如何构建承诺的,它称为ecma-international.org/ecma-262/6.0/index.html#sec-construct是同步调用。依次从ecma-international.org/ecma-262/6.0/…进行调用-我认为旧的github.com/promises-aplus/constructor-spec是更好的来源。
本杰明·格伦鲍姆

2
@BenjaminGruenbaum:只是指向JavaScript Promise Callback是否异步执行:-)
Bergi 2015年

1
@EugeneHoza它曾经是(很久以前)的,但后来被更改为显示构造器模式(这被认为更安全)。我曾经喜欢它-但回想起来,我不确定它实际上是否更安全-这只是一个权衡。您可以在此处阅读有关它的信息:blog.domenic.me/the-revealing-constructor-pattern-此外,如果您对此有热情(当然,在阅读相关背景之后)-非常欢迎您提出建议TC39引入另一个API。
本杰明·格林鲍姆

51

我想在这里加2美分。正好考虑“创建es6 Promise而不开始解决它”的问题,我解决了创建包装器函数并调用包装器函数的问题。码:

假设我们有一个f返回Promise的函数

/** @return Promise<any> */
function f(args) {
   return new Promise(....)
}

// calling f()
f('hello', 42).then((response) => { ... })

现在,我想准备一个呼叫,f('hello', 42)而无需实际解决:

const task = () => f('hello', 42) // not calling it actually

// later
task().then((response) => { ... })

希望这会帮助某人:)


Promise.all()如果我想准备调用f1('super')f2('rainbow'),则按注释中的要求进行引用(并由@Joe Frambach回答),2个返回promise的函数

const f1 = args => new Promise( ... )
const f2 = args => new Promise( ... )

const tasks = [
  () => f1('super'),
  () => f2('rainbow')
]

// later
Promise.all(tasks.map(t => t()))
  .then(resolvedValues => { ... })

如何使用Promise.all()
Frondor

任务= [()=> f(1),()=> f(2)]; Promise.all(tasks.map(t => t()))。then(...
Frambot

3

如何采用更全面的方法?

您可以编写一个构造函数,该构造函数返回以.resolve().reject()方法修饰的新Promise 。

您可能会选择命名构造函数Deferred-javascript promises历史中有很多优先级的术语。

function Deferred(fn) {
    fn = fn || function(){};

    var resolve_, reject_;

    var promise = new Promise(function(resolve, reject) {
        resolve_ = resolve;
        reject_ = reject;
        fn(resolve, reject);
    });

    promise.resolve = function(val) {
        (val === undefined) ? resolve_() : resolve_(val);
        return promise;//for chainability
    }
    promise.reject = function(reason) {
        (reason === undefined) ? reject_() : reject_(reason);
        return promise;//for chainability
    }
    promise.promise = function() {
        return promise.then(); //to derive an undecorated promise (expensive but simple).
    }

    return promise;
}

通过返回经过修饰的promsie而不是简单的对象,除修饰外,所有promise的自然方法/属性仍然可用。

此外,通过处理fn,如果您需要/选择在Deferred上使用它,则显示模式仍然可用。

演示

现在,有了该Deferred()实用程序,您的代码实际上与jQuery示例相同。

var deferreds = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return deferreds[key].promise();
  }
  var def = Deferred();    
  deferreds[key] = def;
  return def.promise();
}

我不知道,这似乎重新介绍了我们从递延转换为Promise和构造方法时要避免的所有事情。另外,你的实现是完全过于复杂,并包含了几个错误:1)如果你给Deferred一个回调参数,它应该被称为回来的递延B)测试value/reasonundefined绝对不必要C).resolve.reject永远需要被链接d)您的.promise方法不会返回未修饰的承诺
Bergi 2015年

我一直对设计Deferred感到不安。我敢肯定它们是罕见的,但是在某些情况下必须延迟使用,即在创建Promise时无法定义结算方式的情况下。我不理解(a),我愿意对(b)说服,而您对(c)肯定是正确的。
Roamer-1888

我的意思是您应该fn(promise)(而fn(deferred)不是实际上)代替fn(resolve, reject)。是的,我当然可以看到可以存储在某个地方并公开.fulfill.reject方法的“解析程序对象”的需求,但是我认为这些不应实现promise接口。
Bergi 2015年

@Bergi,据我所知,例如jQuery,将Deferred本身显示为回调arg。也许我错过了一些东西,但是我不明白为什么延迟的实现不应该模仿new Promise(fn)揭示正义resolve和正义的事实实践reject
Roamer-1888 2015年

同样,尽管when.js文档不鼓励使用它when.defer,但它的确承认in certain (rare) scenarios it can be convenient to have access to both the promise and it's associated resolving functions。如果可以相信那句话,那么我上面提供的内容似乎是合理的(在极少数情况下)。
Roamer-1888 2015年

1

在JavaScript领域,事情正在逐渐好转,但这是情况仍然不必要地复杂的一种情况。这是一个简单的帮助程序,用于公开“解析”和“拒绝”功能:

Promise.unwrapped = () => {
  let resolve, reject, promise = new Promise((_resolve, _reject) => {
    resolve = _resolve, reject = _reject
  })
  promise.resolve = resolve, promise.reject = reject
  return promise
}

// a contrived example

let p = Promise.unwrapped()
p.then(v => alert(v))
p.resolve('test')

显然曾经有一个Promise.defer帮助者,但即使如此,也坚持要求推迟的对象与承诺本身分开……

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.