import { config } from './config'; import instrument from './instrument';

import {

isFunction,
now

} from './utils';

import {

noop,
subscribe,
initializePromise,
invokeCallback,
FULFILLED,
REJECTED

} from './-internal';

import all from './promise/all'; import race from './promise/race'; import Resolve from './promise/resolve'; import Reject from './promise/reject';

var guidKey = 'rsvp_' + now() + '-'; var counter = 0;

function needsResolver() {

throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');

}

function needsNew() {

throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");

} export default Promise; /**

Promise objects represent the eventual result of an asynchronous operation. The
primary way of interacting with a promise is through its `then` method, which
registers callbacks to receive either a promise’s eventual value or the reason
why the promise cannot be fulfilled.

Terminology
-----------

- `promise` is an object or function with a `then` method whose behavior conforms to this specification.
- `thenable` is an object or function that defines a `then` method.
- `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
- `exception` is a value that is thrown using the throw statement.
- `reason` is a value that indicates why a promise was rejected.
- `settled` the final resting state of a promise, fulfilled or rejected.

A promise can be in one of three states: pending, fulfilled, or rejected.

Promises that are fulfilled have a fulfillment value and are in the fulfilled
state.  Promises that are rejected have a rejection reason and are in the
rejected state.  A fulfillment value is never a thenable.

Promises can also be said to *resolve* a value.  If this value is also a
promise, then the original promise's settled state will match the value's
settled state.  So a promise that *resolves* a promise that rejects will
itself reject, and a promise that *resolves* a promise that fulfills will
itself fulfill.

Basic Usage:
------------

```js
var promise = new Promise(function(resolve, reject) {
  // on success
  resolve(value);

  // on failure
  reject(reason);
});

promise.then(function(value) {
  // on fulfillment
}, function(reason) {
  // on rejection
});
```

Advanced Usage:
---------------

Promises shine when abstracting away asynchronous interactions such as
`XMLHttpRequest`s.

```js
function getJSON(url) {
  return new Promise(function(resolve, reject){
    var xhr = new XMLHttpRequest();

    xhr.open('GET', url);
    xhr.onreadystatechange = handler;
    xhr.responseType = 'json';
    xhr.setRequestHeader('Accept', 'application/json');
    xhr.send();

    function handler() {
      if (this.readyState === this.DONE) {
        if (this.status === 200) {
          resolve(this.response);
        } else {
          reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
        }
      }
    };
  });
}

getJSON('/posts.json').then(function(json) {
  // on fulfillment
}, function(reason) {
  // on rejection
});
```

Unlike callbacks, promises are great composable primitives.

```js
Promise.all([
  getJSON('/posts'),
  getJSON('/comments')
]).then(function(values){
  values[0] // => postsJSON
  values[1] // => commentsJSON

  return values;
});
```

@class RSVP.Promise
@param {function} resolver
@param {String} label optional string for labeling the promise.
Useful for tooling.
@constructor

*/ function Promise(resolver, label) {

this._id = counter++;
this._label = label;
this._state = undefined;
this._result = undefined;
this._subscribers = [];

if (config.instrument) {
  instrument('created', this);
}

if (noop !== resolver) {
  if (!isFunction(resolver)) {
    needsResolver();
  }

  if (!(this instanceof Promise)) {
    needsNew();
  }

  initializePromise(this, resolver);
}

}

Promise.cast = Resolve; // deprecated Promise.all = all; Promise.race = race; Promise.resolve = Resolve; Promise.reject = Reject;

Promise.prototype = {

constructor: Promise,

_guidKey: guidKey,

_onerror: function (reason) {
  config.trigger('error', reason);
},

/**

The primary way of interacting with a promise is through its `then` method,
which registers callbacks to receive either a promise's eventual value or the
reason why the promise cannot be fulfilled.

```js
findUser().then(function(user){
  // user is available
}, function(reason){
  // user is unavailable, and you are given the reason why
});
```

Chaining
--------

The return value of `then` is itself a promise.  This second, 'downstream'
promise is resolved with the return value of the first promise's fulfillment
or rejection handler, or rejected if the handler throws an exception.

```js
findUser().then(function (user) {
  return user.name;
}, function (reason) {
  return 'default name';
}).then(function (userName) {
  // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
  // will be `'default name'`
});

findUser().then(function (user) {
  throw new Error('Found user, but still unhappy');
}, function (reason) {
  throw new Error('`findUser` rejected and we're unhappy');
}).then(function (value) {
  // never reached
}, function (reason) {
  // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
  // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
});
```
If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.

```js
findUser().then(function (user) {
  throw new PedagogicalException('Upstream error');
}).then(function (value) {
  // never reached
}).then(function (value) {
  // never reached
}, function (reason) {
  // The `PedgagocialException` is propagated all the way down to here
});
```

Assimilation
------------

Sometimes the value you want to propagate to a downstream promise can only be
retrieved asynchronously. This can be achieved by returning a promise in the
fulfillment or rejection handler. The downstream promise will then be pending
until the returned promise is settled. This is called *assimilation*.

```js
findUser().then(function (user) {
  return findCommentsByAuthor(user);
}).then(function (comments) {
  // The user's comments are now available
});
```

If the assimliated promise rejects, then the downstream promise will also reject.

```js
findUser().then(function (user) {
  return findCommentsByAuthor(user);
}).then(function (comments) {
  // If `findCommentsByAuthor` fulfills, we'll have the value here
}, function (reason) {
  // If `findCommentsByAuthor` rejects, we'll have the reason here
});
```

Simple Example
--------------

Synchronous Example

```javascript
var result;

try {
  result = findResult();
  // success
} catch(reason) {
  // failure
}
```

Errback Example

```js
findResult(function(result, err){
  if (err) {
    // failure
  } else {
    // success
  }
});
```

Promise Example;

```javascript
findResult().then(function(result){
  // success
}, function(reason){
  // failure
});
```

Advanced Example
--------------

Synchronous Example

```javascript
var author, books;

try {
  author = findAuthor();
  books  = findBooksByAuthor(author);
  // success
} catch(reason) {
  // failure
}
```

Errback Example

```js

function foundBooks(books) {

}

function failure(reason) {

}

findAuthor(function(author, err){
  if (err) {
    failure(err);
    // failure
  } else {
    try {
      findBoooksByAuthor(author, function(books, err) {
        if (err) {
          failure(err);
        } else {
          try {
            foundBooks(books);
          } catch(reason) {
            failure(reason);
          }
        }
      });
    } catch(error) {
      failure(err);
    }
    // success
  }
});
```

Promise Example;

```javascript
findAuthor().
  then(findBooksByAuthor).
  then(function(books){
    // found books
}).catch(function(reason){
  // something went wrong
});
```

@method then
@param {Function} onFulfilled
@param {Function} onRejected
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise}

*/

then: function(onFulfillment, onRejection, label) {
  var parent = this;
  var state = parent._state;

  if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
    if (config.instrument) {
      instrument('chained', this, this);
    }
    return this;
  }

  parent._onerror = null;

  var child = new this.constructor(noop, label);
  var result = parent._result;

  if (config.instrument) {
    instrument('chained', parent, child);
  }

  if (state) {
    var callback = arguments[state - 1];
    config.async(function(){
      invokeCallback(state, child, callback, result);
    });
  } else {
    subscribe(parent, child, onFulfillment, onRejection);
  }

  return child;
},

/**

`catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
as the catch block of a try/catch statement.

```js
function findAuthor(){
  throw new Error('couldn't find that author');
}

// synchronous
try {
  findAuthor();
} catch(reason) {
  // something went wrong
}

// async with promises
findAuthor().catch(function(reason){
  // something went wrong
});
```

@method catch
@param {Function} onRejection
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise}

*/

'catch': function(onRejection, label) {
  return this.then(null, onRejection, label);
},

/**

`finally` will be invoked regardless of the promise's fate just as native
try/catch/finally behaves

Synchronous example:

```js
findAuthor() {
  if (Math.random() > 0.5) {
    throw new Error();
  }
  return new Author();
}

try {
  return findAuthor(); // succeed or fail
} catch(error) {
  return findOtherAuther();
} finally {
  // always runs
  // doesn't affect the return value
}
```

Asynchronous example:

```js
findAuthor().catch(function(reason){
  return findOtherAuther();
}).finally(function(){
  // author was either found, or not
});
```

@method finally
@param {Function} callback
@param {String} label optional string for labeling the promise.
Useful for tooling.
@return {Promise}

*/

'finally': function(callback, label) {
  var constructor = this.constructor;

  return this.then(function(value) {
    return constructor.resolve(callback()).then(function(){
      return value;
    });
  }, function(reason) {
    return constructor.resolve(callback()).then(function(){
      throw reason;
    });
  }, label);
}

};