GithubHelp home page GithubHelp logo

prophecy's Introduction

Prophecy โ€“ Angular 2.0 Deferred/Promise

Status: In-Development

This project makes available an ES6 Deferred implementation, using ES6 Promises. Also included is a utility mock implementation of Promise with a corresponding PromiseBackend which allows flushing of the Promise's underlying microtask queue, allowing developers to write synchronous tests against Promise-based libraries.

Install

$ npm install --save angular/prophecy

Deferred

The Deferred class is a small wrapper around Promise which lifts the requirement of resolving the promise within the resolver function that gets passed to the Promise constructor.

Example with vanilla Promise:

var promise = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve('done');
  }, 1000);
});
promise.then(function(val) {
  console.log(val);
});
//Logs "done" to the console in 1 second

Example with Deferred without using PromiseMock:

import {Deferred} from './node_modules/prophecy/src/Deferred';
var deferred = new Deferred();
deferred.promise.then(function(val) {
  console.log(val);
});
setTimeout(function() {
  deferred.resolve('done');
}, 1000);
//Logs "done" to the console in 1 secondn

Deferred Instance Methods and Properties

name description
resolve(value) Alias to promise.resolve()
reject(reason) Alias to promise.reject()
promise Promise instance, used to chain actions to be executed upon fulfillment of the promise

PromiseMock Module (For Testing)

The PromiseMock module contains two classes that allow synchronous testing of promise-based APIs. The PromiseBackend class provides methods to register and unregister the PromiseMock class as the global Promise constructor, as well as methods to flush the queue of pending operations registered by the PromiseMock. The PromiseMock implementation is exactly the same as the native/Traceur ES6 Promise, except that it adds its pending tasks to the flushable PromiseBackend queue instead of a hidden microtask queue.

Example test of Deferred using PromiseBackend:

import {PromiseBackend} from './node_modules/prophecy/src/PromiseMock';
import {Deferred} from './node_modules/prophecy/src/Deferred';
describe('.resolve()', function() {
  it('should call the resolver\'s resolve function with the correct value',
    function() {
      var resolveSpy = jasmine.createSpy('resolveSpy');
      PromiseBackend.forkZone().run(function() {
        var deferred = new Deferred();
        deferred.promise.then(resolveSpy);
        deferred.resolve('Flush me!');
        PromiseBackend.flush(true);
      });

      expect(resolveSpy).toHaveBeenCalledWith('Flush me!');
  });
});

PromiseBackend

The PromiseBackend class is completely static. This class manages the process of patching the global object with PromiseMock as well as flushing any pending promise fulfillment operations. The PromiseBackend keeps a single queue of pending tasks, which is shared by all promises.

The PromiseBackend provides a convenience method to create a zone within which tests can be executed, which will automatically patch and unpatch window.Promise. The zone will also verify that no outstanding requests are waiting to be flushed.

beforeEach(function() {
  //No need for PromiseBackend.patchWithMock(), or an afterEach() to unpatch
  this.zone = PromiseBackend.forkZone();
});
it('should resolve with a smiley', function() {
  this.zone.run(function() {
    var resolveSpy = jasmine.createSpy();
    var backend = new PromiseBackend();
    new Promise(function(resolve) {
      resolve(':)');
    }).
    then(resolveSpy);
    backend.flush();
    expect(resolveSpy).toHaveBeenCalledWith(':)');
  });
})

The flush method is called in lieu of waiting for the next VM turn, and prevents the need for writing async tests using setTimeout. Example writing an test that waits for an async operation with setTimeout:

it('should resolve with a smiley', function(done) {
  var resolveSpy = jasmine.createSpy();
  new Promise(function(resolve) {
    resolve(':)');
  }).
  then(resolveSpy);
  setTimeout(function() {
    expect(resolveSpy).toHaveBeenCalledWith(':)');
    done();
  }, 0);
});

With PromiseMock and PromiseBackend.flush(), this same test can be expressed as:

it('should resolve with a smiley', function() {
  var resolveSpy = jasmine.createSpy();
  PromiseBackend.forkZone().run(function() {
    new Promise(function(resolve) {
      resolve(':)');
    }).
    then(resolveSpy);
    PromiseBackend.flush(true);
  });

  expect(resolveSpy).toHaveBeenCalledWith(':)');
});

PromiseBackend Methods and Properties

name description
static setGlobal(global:Object) global context to which the native implementation of Promise is attached (default: window)
static flush(recursiveFlush=false) Flushes all tasks that have been queued for execution. If recursiveFlush is true, the backend will continue flushing until the queue is empty, including tasks that have been added since flushing began. Returns the PromiseBackend instance to allow chaining.
static executeAsap(fn:Function) Add a function to the queue to be executed on the next flush
static restoreNativePromise() Restore the native Promise implementation to the global object
static patchWithMock() Replace the global Promise constructor with PromiseMock
static verifyNoOutstandingTasks() Throw if tasks are in the queue waiting to flush
static zone:forkZone() Creates and returns a new zone which automatically patches window.Promise with the PromiseMock before execution, and restores the original promise after execution.
static queue:Array.<Function> Array of functions to be executed on next flush, populated by executeAsap(). Note that this is undefined until an instance of PromiseBackend is created.
global:Object The global context within which PromiseBackend is operating, default: window

Design Doc (superceded by implementation in this project).

TODO

  • Add A+ tests for PromiseMock. This implementation is copied from Traceur (which is ported from V8). The Traceur implementation is already passing A+ tests. This project should have the tests as well.
  • Add src/index.js to export items that should be available at runtime.
  • Add build process

prophecy's People

Contributors

btford avatar caitp avatar jeffbcross avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

prophecy's Issues

Real? Bower?

@jeffbcross, will prophesy be used by Angular 2.0? Have you considered publishing on npm and bower? If not, can you suggest any other es6 promise complaint deferred implementation? It doesn't make sense to pull in traceur.

Rename for project

  1. The Deferred constructor shouldn't be the focal point of this project. This project should be treated as a small wrapper around the native Promise constructor to improve testability. Deferred is just a means of flattening and un-throw-safing the Promise constructor in order to use in the context of a zone, and isn't necessarily the recommended approach for using Promises with Angular 2.
  2. Other angular 2 projects have cool names, like watchtower. This project should have a cooler name.

ES6 Arrow functions 'this' is from lexical scope of enclosing block (no need to use Function#bind())

https://github.com/angular/deferred/blob/master/src/Deferred.js#L3-L6 is using bind when it's not really necessary in ES6. Arrow functions should perform better once supported natively, and Traceur's implementation should already perform better due to not creating a new function.

Traceur's version of this also improves performance and avoids creating a new function, by just creating an alias variable for this and replacing this with the alias within the call.

PromiseBackend#flush (PromiseMock) should warn if missing PromiseBackend.queue

one of the failing tests in http#18 is related to this.

Since we're just getting a TypeError, it makes it sort of tricky for the person writing tests to fix this, it would be useful to be a bit more informative.

In angular-mocks, trying to flush with no outstanding requests will cause a test to fail, so we probably do want to fail in this case, but it's probably a better idea to make the error explicitly about "no promises to flush" or something.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.