Обещания JavaScript обеспечивают надежную модель программирования для будущего развития JavaScript.
Так что здесь я играю с обещаниями.
Сначала мне нужно немного файла package.json:
{ "name": "promises", "scripts": { "test": "node node_modules/mocha/bin/mocha" }, "devDependencies": { "chai": "^1.10.0", "mocha": "^2.0.1" }, "dependencies": { "q": "^1.1.2" } }
Теперь я могу написать свой первый тест (test / promises_test.js):
var Q = require('q'); var expect = require('chai').expect; describe('promises', function() { it('can be resolved', function(done) { var promise = Q.Promise(function(resolve) { resolve(); }); promise.then(function() { done(); }); }); });
Обратите внимание, что функция «it» принимает параметр функции «done», чтобы гарантировать, что тест ожидает, пока обещание не будет выполнено. Удалите вызов функции done () или resolution (), и проверка завершится по таймауту.
it('can pass resolution value', function(done) { var promise = Q.Promise(function(resolve) { resolve(6*8); }); promise.then(function(value) { expect(value).to.equal(42); done(); }); });
Этот тест не пройден, но из-за тайм-аута. Причина в том, что сделано никогда не называется. Давайте улучшим тест.
it('can pass resolution value', function(done) { var promise = Q.Promise(function(resolve) { resolve(6*8); }); promise.done(function(value) { expect(value).to.equal(42); done(); }); });
Использование «done ()» вместо «then ()» означает, что цепочка обещаний завершена. Если мы не справились с ошибками, то готово вызовет исключение. Тест больше не истекает, но хорошо проваливается:
promises V can be resolved 1) can pass resolution value 1 passing (11ms) 1 failing 1) promises can pass resolution value: Uncaught AssertionError: expected 48 to equal 42 + expected - actual +42 -48 at C:\Users\jhannes\experiments\promises\test\promises_test.js:22:24
И мы можем это исправить:
it('can pass resolution value', function(done) { var promise = Q.Promise(function(resolve) { resolve(6*7); }); promise.done(function(value) { expect(value).to.equal(42); done(); }); });
Урок: Всегда заканчивайте цепочку обещаний с помощью done ().
Чтобы разделить это, вы можете разделить then и Done:
it('can pass resolution value', function(done) { var promise = Q.Promise(function(resolve) { resolve(6*7); }); promise.then(function(value) { expect(value).to.equal(42); }).done(done); });
Для этого есть еще один способ:
it('can return the promise to mocha', function() { var promise = Q.Promise(function(resolve) { resolve(6*7); }); return promise.then(function(value) { expect(value).to.equal(42); }); });
Но что такое цепочка обещаний?
it('can pass values in a promise chain', function(done) { var promise = Q.Promise(function(resolve) { resolve(6); }); promise.then(function(v) { return v*7; }).done(function(v) { expect(v).to.equal(42); }).done(done); });
Это очень круто, когда нам нужно несколько обещаний:
it('can resolve multiple promises', function(done) { var promise = Q.Promise(function(resolve) { resolve(['abc', 'def', 'gh']); }); promise.then(function(array) { return Q.all(array.map(function(v) { return Q.Promise(function(resolve) { resolve(v.length); }) })); }).done(function(lengthArray) { expect(lengthArray).to.eql([3,3,2]); }).done(done); });
Обратите внимание, что done вызывается только тогда, когда ВСЕ строки рассчитаны (асинхронно).
Поначалу это может показаться странным, но чрезвычайно полезно при работе с графами объектов:
var savePurchaseOrder = function(purchaseOrder) { return orderDao .save(purchaseOrder) .then(function(orderId) { purchaseOrder.orderLines.forEach(function(line) { line.orderId = orderId; }); return Q.all( purchaseOrder.orderLines.map(orderLineDao.save)); }); };
Здесь методы сохранения dao.orderDao и orderLineDao оба возвращают обещания. Наша функция «savePurchaseOrder» также возвращает обещание, которое разрешается, когда все сохраняется. И все происходит асинхронно.
Хорошо, вернемся к основам об обещаниях.
it('can reject a promise', function(done) { var promise = Q.Promise(function(resolve, reject) { reject('something went wrong'); }); promise.then(null, function(error) { expect(error).to.equal('something went wrong'); }).done(done); });
Здесь вызывается вторая функция done (). Мы можем использовать «fail ()» в качестве ярлыка:
it('can reject a promise', function(done) { var promise = Q.Promise(function(resolve, reject) { reject('something went wrong'); }); promise.fail(function(error) { expect(error).to.equal('something went wrong'); done(); }); // trailing promise! });
Но это не так хорошо. Если сравнение не удастся, этот тест истечет! Это лучше:
it('can reject a promise', function(done) { var promise = Q.Promise(function(resolve, reject) { reject('something went wrong'); }); promise.fail(function(error) { expect(error).to.equal('something went wrong'); }).done(done); });
Конечно, нам нужно обрабатывать и неожиданные события:
it('treats errors as rejections', function(done) { var promise = Q.Promise(function(resolve, reject) { throw new Error('whoops'); }); promise.fail(function(error) { expect(error).to.eql(new Error('whoops')); }).done(done); });
И конечно: что-то может потерпеть неудачу в середине цепи:
it('treats errors as rejections', function(done) { var promise = Q.Promise(function(resolve, reject) { resolve(null); }); promise.then(function(value) { return value.length; }).fail(function(error) { expect(error.message).to.eql("Cannot read property 'length' of null"); }).done(done); });
Ошибка автоматически распространяется на первый обработчик ошибки:
it('propagates failure to the first failure handler', function(done) { var promise = Q.Promise(function(resolve, reject) { resolve(null); }); promise.then(function(value) { return value.length; // Throws Error }).then(function(length) { this.test.error("never called"); }).then(function(number) { this.test.error("never called"); }).fail(function(error) { expect(error.message).to.eql("Cannot read property 'length' of null"); }).done(done); });
Мне потребовалось некоторое время, чтобы освоиться с Promises, но когда я это сделал, это немного упростило мой код JavaScript.
Вы можете найти весь исходный код здесь. Кроме того, обязательно ознакомьтесь со слайдами Скотта Сойета о функциональном JavaScript , чтобы узнать больше об обещаниях, карри и других вкусных функциональных вещах.
Спасибо моему бывшему коллеге и соратнику изгнанию Санату за вдохновение написать эту статью.