在功能范围之外解决Javascript Promise

时间:2014-10-01 20:40:45

标签: javascript promise es6-promise

我一直在使用ES6 Promise。

通常,像这样构建和使用Promise

new Promise(function(resolve, reject){
    if (someCondition){
        resolve();
    } else {
        reject();
    } 
});

但是为了灵活性,我一直在做类似下面的事情来解决问题。

var outsideResolve;
var outsideReject;
new Promise(function(resolve, reject) { 
    outsideResolve = resolve; 
    outsideReject = reject; 
});

以后

onClick = function(){
    outsideResolve();
}

这很好用,但有更简单的方法吗?如果没有,这是一个好习惯吗?

22 个答案:

答案 0 :(得分:99)

简单:

{{1}}

答案 1 :(得分:75)

这里的派对迟到了,但另一种方法是使用Deferred对象。你基本上拥有相同数量的样板,但是如果你想传递它们并且可能在它们的定义之外解决它,它会很方便。

天真实施:

class Deferred {
  constructor() {
    this.promise = new Promise((resolve, reject)=> {
      this.reject = reject
      this.resolve = resolve
    })
  }
}

function asyncAction() {
  var dfd = new Deferred()

  setTimeout(()=> {
    dfd.resolve(42)
  }, 500)

  return dfd.promise
}

asyncAction().then(result => {
  console.log(result) // 42
})

ES5版本:

function Deferred() {
  var self = this;
  this.promise = new Promise(function(resolve, reject) {
    self.reject = reject
    self.resolve = resolve
  })
}

function asyncAction() {
  var dfd = new Deferred()

  setTimeout(function() {
    dfd.resolve(42)
  }, 500)

  return dfd.promise
}

asyncAction().then(function(result) {
  console.log(result) // 42
})

答案 2 :(得分:70)

不,没有其他方法可以做到这一点 - 我唯一可以说的是这个用例并不常见。就像费利克斯在评论中所说的那样 - 你所做的将会持续发挥作用。

值得一提的是,promise构造函数以这种方式运行的原因是抛出安全性 - 如果您的代码在promise构造函数内运行时没有预料到会发生异常,它将变成拒绝,这种形式抛出安全 - 将抛出的错误转换为拒绝是很重要的,有助于维护可预测的代码。

对于这个抛出安全的原因,承诺构造函数被选择为延迟(这是一种替代的承诺构造方式,允许你正在做的事情) - 至于最佳实践 - 我传递元素和改为使用promise构造函数:

var p = new Promise(function(resolve, reject){
    this.onclick = resolve;
}.bind(this));

出于这个原因 - 只要你可以使用promise构造函数而不是导出函数 - 我建议你使用它。每当你可以避免两者 - 避免两者和链。

注意,你永远不应该对if(condition)之类的东西使用promise构造函数,第一个例子可以写成:

var p = Promise[(someCondition)?"resolve":"reject"]();

答案 3 :(得分:16)

我在2015年为我的框架提出了一个解决方案。我称这种承诺为任务

function createPromise(handler){
  var _resolve, _reject;

  var promise = new Promise(function(resolve, reject){
    _resolve = resolve; 
    _reject = reject;

    handler(resolve, reject);
  })

  promise.resolve = _resolve;
  promise.reject = _reject;

  return promise;
}

var promise = createPromise()
promise.then(function(data){ alert(data) })

promise.resolve(200) // resolve from outside

答案 4 :(得分:13)

我喜欢@JonJaques的回答,但我想更进一步。

如果您绑定thencatch然后绑定Deferred对象,那么它会完全实现Promise API,您可以将其视为承诺和await它等等。

class DeferredPromise {
  constructor() {
    this._promise = new Promise((resolve, reject) => {
      // assign the resolve and reject functions to `this`
      // making them usable on the class instance
      this.resolve = resolve;
      this.reject = reject;
    });
    // bind `then` and `catch` to implement the same interface as Promise
    this.then = this._promise.then.bind(this._promise);
    this.catch = this._promise.catch.bind(this._promise);
    this[Symbol.toStringTag] = 'Promise';
  }
}

const deferred = new DeferredPromise();
console.log('waiting 2 seconds...');
setTimeout(() => {
  deferred.resolve('whoa!');
}, 2000);

async function someAsyncFunction() {
  const value = await deferred;
  console.log(value);
}

someAsyncFunction();

答案 5 :(得分:9)

辅助方法可以减轻这些额外开销,并为您提供相同的jQuery感觉。

function Deferred() {
    let resolve;
    let reject;
    const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    });
    return { promise, resolve, reject };
}

用法是

const { promise, resolve, reject } = Deferred();
displayConfirmationDialog({
    confirm: resolve,
    cancel: reject
});
return promise;

类似于jQuery

const dfd = $.Deferred();
displayConfirmationDialog({
    confirm: dfd.resolve,
    cancel: dfd.reject
});
return dfd.promise();

虽然在一个用例中这个简单的原生语法很好

return new Promise((resolve, reject) => {
    displayConfirmationDialog({
        confirm: resolve,
        cancel: reject
    });
});

答案 6 :(得分:7)

我正在使用一个辅助函数来创建所谓的“统一承诺”-

function flatPromise() {

    let resolve, reject;

    const promise = new Promise((res, rej) => {
      resolve = res;
      reject = rej;
    });

    return { promise, resolve, reject };
}

我正在这样使用它-

function doSomethingAsync() {

    // Get your promise and callbacks
    const { resolve, reject, promise } = flatPromise();

    // Do something amazing...
    setTimeout(() => {
        resolve('done!');
    }, 500);

    // Pass your promise to the world
    return promise;

}

查看完整的工作示例-

function flatPromise() {

    let resolve, reject;

    const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    });

    return { promise, resolve, reject };
}

function doSomethingAsync() {
    
    // Get your promise and callbacks
    const { resolve, reject, promise } = flatPromise();

    // Do something amazing...
    setTimeout(() => {
        resolve('done!');
    }, 500);

    // Pass your promise to the world
    return promise;
}

(async function run() {

    const result = await doSomethingAsync()
        .catch(err => console.error('rejected with', err));
    console.log(result);

})();

编辑: 我创建了一个名为flat-promise的NPM程序包,并且该代码也可以在on GitHub中使用。

答案 7 :(得分:3)

是的,可以。通过为浏览器环境使用CustomEvent API。并在node.js环境中使用事件发射器项目。由于问题中的代码段是针对浏览器环境的,因此这里是一个可行的示例。

function myPromiseReturningFunction(){
  return new Promise(resolve => {
    window.addEventListener("myCustomEvent", (event) => {
       resolve(event.detail);
    }) 
  })
}


myPromiseReturningFunction().then(result => {
   alert(result)
})

document.getElementById("p").addEventListener("click", () => {
   window.dispatchEvent(new CustomEvent("myCustomEvent", {detail : "It works!"}))
})
<p id="p"> Click me </p>

我希望这个答案有用!

答案 8 :(得分:2)

您可以将Promise包装在一个类中。

class Deferred {
    constructor(handler) {
        this.promise = new Promise((resolve, reject) => {
            this.reject = reject;
            this.resolve = resolve;
            handler(resolve, reject);
        });

        this.promise.resolve = this.resolve;
        this.promise.reject = this.reject;

        return this.promise;
    }
    promise;
    resolve;
    reject;
}

// How to use.
const promise = new Deferred((resolve, reject) => {
  // Use like normal Promise.
});

promise.resolve(); // Resolve from any context.

答案 9 :(得分:2)

我们的解决方案是使用闭包来存储解析/拒绝函数,并附加一个函数来扩展promise本身。

以下是模式:

function getPromise() {

    var _resolve, _reject;

    var promise = new Promise((resolve, reject) => {
        _reject = reject;
        _resolve = resolve;
    });

    promise.resolve_ex = (value) => {
       _resolve(value);
    };

    promise.reject_ex = (value) => {
       _reject(value);
    };

    return promise;
}

使用它:

var promise = getPromise();

promise.then(value => {
    console.info('The promise has been fulfilled: ' + value);
});

promise.resolve_ex('hello');  
// or the reject version 
//promise.reject_ex('goodbye');

答案 10 :(得分:2)

感谢在此主题中发布的每个人。我创建了一个模块,该模块包括前面所述的Defer()对象以及在其上构建的其他一些对象。它们都利用Promises和简洁的Promise回调语法在程序内实现通信/事件处理。

  • 推迟:可以远程解决的承诺失败(不在其主体范围内)
  • 延迟:在给定时间后自动解决的承诺
  • 超时:在给定时间后自动失败的承诺。
  • 周期:可重新触发的使用Promise语法管理事件的承诺
  • 队列:基于Promise链接的执行队列。

    rp = require("repeatable-promise")

    https://github.com/CABrouwers/repeatable-promise

答案 11 :(得分:1)

此处的许多答案与this article中的最后一个示例相似。 我正在缓存多个Promises,并且resolve()reject()函数可以分配给任何变量或属性。结果,我可以使这段代码更加紧凑:

function defer(obj) {
    obj.promise = new Promise((resolve, reject) => {
        obj.resolve = resolve;
        obj.reject  = reject;
    });
}

以下是使用此版本的defer()来将FontFace负载承诺与另一个异步过程结合在一起的简化示例:

function onDOMContentLoaded(evt) {
    let all = []; // array of Promises
    glob = {};    // global object used elsewhere
    defer(glob);
    all.push(glob.promise);
    // launch async process with callback = resolveGlob()

    const myFont = new FontFace("myFont", "url(myFont.woff2)");
    document.fonts.add(myFont);
    myFont.load();
    all.push[myFont];
    Promise.all(all).then(() => { runIt(); }, (v) => { alert(v); });
}
//...
function resolveGlob() {
    glob.resolve();
}
function runIt() {} // runs after all promises resolved 

更新:如果要封装对象,有2种选择:

function defer(obj = {}) {
    obj.promise = new Promise((resolve, reject) => {
        obj.resolve = resolve;
        obj.reject  = reject;
    });
    return obj;
}
let deferred = defer();

class Deferred {
    constructor() {
        this.promise = new Promise((resolve, reject) => {
            this.resolve = resolve;
            this.reject  = reject;
        });
    }
}
let deferred = new Deferred();

答案 12 :(得分:1)

接受的答案是错误的。使用范围和引用非常容易,尽管它可能会使Promise 纯粹主义者生气:

const createPromise = () => {
    let resolver;
    return [
        new Promise((resolve, reject) => {
            resolver = resolve;
        }),
        resolver,
    ];
};

const [ promise, resolver ] = createPromise();
promise.then(value => console.log(value));
setTimeout(() => resolver('foo'), 1000);

创建诺言时,我们实质上是在获取对resolve函数的引用,然后我们将其返回,以便可以在外部进行设置。

控制台将在一秒钟内输出:

> foo

答案 13 :(得分:0)

为此我写了一个小库。 https://www.npmjs.com/package/@inf3rno/promise.exposed

我使用了其他人编写的工厂方法,但是我也覆盖了thencatchfinally方法,因此您也可以解决那些最初的承诺。

>

在没有外部执行者的情况下解决承诺:

const promise = Promise.exposed().then(console.log);
promise.resolve("This should show up in the console.");

通过外部执行者的setTimeout进行竞速:

const promise = Promise.exposed(function (resolve, reject){
    setTimeout(function (){
        resolve("I almost fell asleep.")
    }, 100000);
}).then(console.log);

setTimeout(function (){
    promise.resolve("I don't want to wait that much.");
}, 100);

如果您不想污染全局名称空间,则有一个无冲突模式:

const createExposedPromise = require("@inf3rno/promise.exposed/noConflict");
const promise = createExposedPromise().then(console.log);
promise.resolve("This should show up in the console.");

答案 14 :(得分:0)

如何创建劫持拒绝并返回的功能?

function createRejectablePromise(handler) {
  let _reject;

  const promise = new Promise((resolve, reject) => {
    _reject = reject;

    handler(resolve, reject);
  })

  promise.reject = _reject;
  return promise;
}

// Usage
const { reject } = createRejectablePromise((resolve) => {
  setTimeout(() => {
    console.log('resolved')
    resolve();
  }, 2000)

});

reject();

答案 15 :(得分:0)

我汇总了完成该工作的要点:https://gist.github.com/thiagoh/c24310b562d50a14f3e7602a82b4ef13

这是您应该如何使用它:

import ExternalizedPromiseCreator from '../externalized-promise';

describe('ExternalizedPromise', () => {
  let fn: jest.Mock;
  let deferredFn: jest.Mock;
  let neverCalledFn: jest.Mock;
  beforeEach(() => {
    fn = jest.fn();
    deferredFn = jest.fn();
    neverCalledFn = jest.fn();
  });

  it('resolve should resolve the promise', done => {
    const externalizedPromise = ExternalizedPromiseCreator.create(() => fn());

    externalizedPromise
      .promise
      .then(() => deferredFn())
      .catch(() => neverCalledFn())
      .then(() => {
        expect(deferredFn).toHaveBeenCalled();
        expect(neverCalledFn).not.toHaveBeenCalled();
        done();
      });

    expect(fn).toHaveBeenCalled();
    expect(neverCalledFn).not.toHaveBeenCalled();
    expect(deferredFn).not.toHaveBeenCalled();

    externalizedPromise.resolve();
  });
  ...
});

答案 16 :(得分:0)

首先在浏览器或节点上启用--allow-natives-syntax

const p = new Promise(function(resolve, reject){
    if (someCondition){
        resolve();
    } else {
        reject();
    } 
});

onClick = function () {
    %ResolvePromise(p, value)
}

答案 17 :(得分:0)

我制作了一个名为manual-promise的库,用来代替Promise。此处的其他答案都不能用作Promise的替代品,因为它们使用代理或包装。

yarn add manual-promise

npn install manual-promise


import { ManualPromise } from "manual-promise";

const prom = new ManualPromise();

prom.resolve(2);

// actions can still be run inside the promise
const prom2 = new ManualPromise((resolve, reject) => {
    // ... code
});


new ManualPromise() instanceof Promise === true

https://github.com/zpxp/manual-promise#readme

答案 18 :(得分:0)

我发现自己在某些情况下也缺少Deferred模式。您始终可以在ES6 Promise的顶部创建一个:

export default class Deferred<T> {
    private _resolve: (value: T) => void = () => {};
    private _reject: (value: T) => void = () => {};

    private _promise: Promise<T> = new Promise<T>((resolve, reject) => {
        this._reject = reject;
        this._resolve = resolve;
    })

    public get promise(): Promise<T> {
        return this._promise;
    }

    public resolve(value: T) {
        this._resolve(value);
    }

    public reject(value: T) {
        this._reject(value);
    }
}

答案 19 :(得分:0)

另一种解决方案,可以从外部解决Promise

 class Lock {
        #lock;  // Promise to be resolved (on  release)
        release;  // Release lock
        id;  // Id of lock
        constructor(id) {
            this.id = id
            this.#lock = new Promise((resolve) => {
                this.release = () => {
                    if (resolve) {
                        resolve()
                    } else {
                        Promise.resolve()
                    }
                }
            })
        }
        get() { return this.#lock }
    }

用法

let lock = new Lock(... some id ...);
...
lock.get().then(()=>{console.log('resolved/released')})
lock.release()  // Excpected 'resolved/released'

答案 20 :(得分:0)

由于我没有找到想要的东西,因此我将分享我在完成此问题后想要实现的目标。

场景:我有3个不同的API,且响应可能相同,因此我想在一个函数中处理Promise的完成和错误处理。这就是我所做的:

  1. 创建处理函数:
  switch (method) {
    case 'get': {
      this.handleHttpPromise(apiService.get(url));
      break;
    }
    case 'post': {
      if (jsonData) {
        this.handleHttpPromise(apiService.post(url, jsonData));
      }
      break;
    }
    // (...)
  }
  1. 将诺言发送给创建的处理程序
{{1}}

答案 21 :(得分:0)

我想分享一些不同的东西,扩展这个话题。

有时您希望在解析时在同一地址(属性或变量)自动重新创建“任务承诺”。可以创建一个可以做到这一点的外部解析器。

具有外部解析器的重复承诺示例。每当调用解析器时,都会在相同的地址/变量/属性处创建一个新的承诺。

let resolvePromise;
let thePromise;

const setPromise = (resolve) => {
  resolvePromise = () => {
    resolve();
    thePromise = new Promise(setPromise);   
  }
}
thePromise = new Promise(setPromise);

(async () => {
  let i = 0;
  while (true) {
    let msg = (i % 2 === 0) ? 'Tick' : 'Tock';
    document.body.innerHTML = msg;
    setTimeout(resolvePromise, 1000);
    await thePromise;
    i++;
  }
})();

https://jsfiddle.net/h3zvw5xr