Обещания 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 , чтобы узнать больше об обещаниях, карри и других вкусных функциональных вещах.
Спасибо моему бывшему коллеге и соратнику изгнанию Санату за вдохновение написать эту статью.