Promises书写更好的JavaScript

使用

Promises更易阅读

$.getJSON('http://hipsterjesus.com/api/', function(data) {
  $('body').append(data.text);
});

如果你用过jQuery,你就会知道这段代码发送了一个GET请求,并且期望得到一个JSON响应。你也可以这样写:

var promise = $.getJSON('http://hipsterjesus.com/api/');

promise.done(function(data) {
  $('body').append(data.text);
});

当失败的时候:

var promise = $.getJSON('http://hipsterjesus.com/api/');

promise.done(function(data) {
  $('body').append(data.text);
});

promise.fail(function() {
  $('body').append('<p>Oh no, something went wrong!</p>');
});

大多数人都不写promise变量:

$.getJSON('http://hipsterjesus.com/api/')

.done(function(data) {
  $('body').append(data.text);
})

.fail(function() {
  $('body').append('<p>Oh no, something went wrong!</p>');
});

jQuery也有一个always方法,无论成功还是失败都会执行:

$.getJSON('http://hipsterjesus.com/api/')

.done(function(data) {
  $('body').append(data.text);
})
.fail(function() {
  $('body').append('<p>Oh no, something went wrong!</p>');
})
.always(function() {
  $('body').append('<p>I promise this will always be added!.</p>');
});

更好的API

var hipsterJesus = {
  html: function() {
    return $.getJSON('http://hipsterjesus.com/api/').then(function(data) {
      return data.text;
    });
  }
};

我们返回的是一个promise对象,这样的话,任何调用这个方法的对象都可以再注册一个done方法到上面。then方法允许我们修改promise的结果,然后传递给下一个链条:

hipsterJesus.html().done(function(html) {
  $("body").append(html);
});

Chaining 调用链

var hipsterJesus = {

  html: function() {
    return $.getJSON('http://hipsterjesus.com/api/').then(function(data) {
      return data.text;
    });
  },

  paragraphs: function() {
    return this.html().then(function(html) {
      return html.replace(/<[^>]+>/g, "").split("");
    });
  }
};

Multiple calls 多个调用

也许promises最炫的就是可以结合多个API调用了,有时候你可能会遇到这样的情况:

var firstData = null;
var secondData = null;

var responseCallback = function() {

  if (!firstData || !secondData)
    return;

  // do something
}

$.get("http://example.com/first", function(data) {
  firstData = data;
  responseCallback();
});

$.get("http://example.com/second", function(data) {
  secondData = data;
  responseCallback();
});

使用promises的话,这种情况变的容易了许多:

var firstPromise = $.get("http://example.com/first");
var secondPromise = $.get("http://example.com/second");

$.when(firstPromise, secondPromise).done(function(firstData, secondData) {
  // do something
});

实现

状态机

因为promise其实就是一个状态机,我们需要定义一些我们随后可能会使用的状态:

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value or error once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers attached by calling .then or .done
  var handlers = [];
}

Transitions 过渡

现在,让我们实现两个关键的过渡,它们可以发生,完成和拒绝:

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function fulfill(result) {
    state = FULFILLED;
    value = result;
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }
}

这让我们拥有了一个比较低级的过渡,但是让我们考虑的更多一些,站在一个更高的层级的称为resolve的过渡上来考虑:

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function fulfill(result) {
    state = FULFILLED;
    value = result;
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      fulfill(result);
    } catch (e) {
      reject(e);
    }
  }
}

注意,resolve是怎样接受一个promise或者一个plain value的,如果它是一个promise,那么就等待它完成。一个promise必须不能通过另外一个proomise来完成,所以它就是这个我们想要暴露的resolve,而不是内部的fulfill函数。我们将会使用一系列工具方法:

/**
 * Check if a value is a Promise and, if it is,
 * return the `then` method of that promise.
 *
 * @param {Promise|Any} value
 * @return {Function|Null}
 */
function getThen(value) {
  var t = typeof value;
  if (value && (t === 'object' || t === 'function')) {
    var then = value.then;
    if (typeof then === 'function') {
      return then;
    }
  }
  return null;
}

/**
 * Take a potentially misbehaving resolver function and make sure
 * onFulfilled and onRejected are only called once.
 *
 * Makes no guarantees about asynchrony.
 *
 * @param {Function} fn A resolver function that may not be trusted
 * @param {Function} onFulfilled
 * @param {Function} onRejected
 */
function doResolve(fn, onFulfilled, onRejected) {
  var done = false;
  try {
    fn(function (value) {
      if (done) return
      done = true
      onFulfilled(value)
    }, function (reason) {
      if (done) return
      done = true
      onRejected(reason)
    })
  } catch (ex) {
    if (done) return
    done = true
    onRejected(ex)
  }
}

Constructing 构造

我们现在已经拥有了一个比较完整的内部状态机,但是我们还没有对外暴露任何用户解析promise或者observing它的方法。让我们来加入它:

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise(fn) {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function fulfill(result) {
    state = FULFILLED;
    value = result;
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      fulfill(result);
    } catch (e) {
      reject(e);
    }
  }

  doResolve(fn, resolve, reject);
}

如你所见,因为我们有其他untrusted resolver,所以我们又重用了方法doResolvefn可以同时调用resolvereject多次,甚至抛出异常。我们应该确保promise只被解析或者拒绝一次,然后就不要再被transition到一个新的状态。

Observing (via .done)

我们现在拥有了一个完整的状态机,但是我们仍然没有办法来观察它的任何改变。我们的终极目标是实现.then.done的语义比.then要简单一些,所以让我们首先来实现它。

我们现在的目标是实现promise.done(onFulfilled, onRejected)

  • 只有哦一个onFulfilled或者onRejected被调用
  • 仅被调用一次
  • 直到下一次tick才会被调用
  • 不管promise是在我们调用.done之前还是之后被解析的,都应该调用
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise(fn) {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function fulfill(result) {
    state = FULFILLED;
    value = result;
    handlers.forEach(handle);
    handlers = null;
  }

  function reject(error) {
    state = REJECTED;
    value = error;
    handlers.forEach(handle);
    handlers = null;
  }

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      fulfill(result);
    } catch (e) {
      reject(e);
    }
  }

  function handle(handler) {
    if (state === PENDING) {
      handlers.push(handler);
    } else {
      if (state === FULFILLED &&
        typeof handler.onFulfilled === 'function') {
        handler.onFulfilled(value);
      }
      if (state === REJECTED &&
        typeof handler.onRejected === 'function') {
        handler.onRejected(value);
      }
    }
  }

  this.done = function (onFulfilled, onRejected) {
    // ensure we are always asynchronous
    setTimeout(function () {
      handle({
        onFulfilled: onFulfilled,
        onRejected: onRejected
      });
    }, 0);
  }

  doResolve(fn, resolve, reject);
}

Observing (via .then)

现在我们实现了.done方法,我们可以很容易地实现.then方法做一些相同的事情,但是在这个过程中生成一个新的Promise

this.then = function (onFulfilled, onRejected) {
  var self = this;
  return new Promise(function (resolve, reject) {
    return self.done(function (result) {
      if (typeof onFulfilled === 'function') {
        try {
          return resolve(onFulfilled(result));
        } catch (ex) {
          return reject(ex);
        }
      } else {
        return resolve(result);
      }
    }, function (error) {
      if (typeof onRejected === 'function') {
        try {
          return resolve(onRejected(error));
        } catch (ex) {
          return reject(ex);
        }
      } else {
        return reject(error);
      }
    });
  });
}

Write Better JavaScript with Promises Implementing

results matching ""

    No results matching ""