많은 언어에서는 일반 시퀀스와 유사한 비동기 패턴을 처리하기 위해 promise, deferred 또는 future라는 흥미로운 솔루션 라이브러리를 포함합니다. JavaScript Promise는 긴밀하게 결합된 인터페이스 대신 우려사항의 분리를 촉진할 수 있습니다. 이 문서에서는 Promises/A 표준을 기반으로 하는 JavaScript Promise에 대해 설명합니다. [http://wiki.commonjs.org/wiki/Promises/A]
Promise 사용 사례:
JavaScript Promise는 미래에 값을 반환하겠다고 약속하는 객체입니다. 동작이 잘 정의된 데이터 개체입니다. Promise에는 세 가지 상태가 있습니다:
거부되거나 완료된 약속은 해결된 것으로 간주됩니다. 약속은 보류 중에서 해결됨으로만 이동할 수 있습니다. 그 이후에는 약속의 상태가 변경되지 않습니다. Promise는 해당 처리가 완료된 후에도 오랫동안 존재할 수 있습니다. 즉, 처리 결과를 여러 번 얻을 수 있습니다. promise.then()을 호출하여 결과를 얻습니다. 이 함수는 promise에 해당하는 처리가 완료될 때까지 반환되지 않습니다. 우리는 여러 가지 약속을 유연하게 하나로 묶을 수 있습니다. 이렇게 연결된 "then" 함수는 새로운 약속이나 가장 빠른 약속을 반환해야 합니다.
이 스타일을 사용하면 동기 코드를 작성하는 것처럼 비동기 코드를 작성할 수 있습니다. 주로 약속을 결합하여 달성됩니다:
왜 귀찮게 하나요? 그냥 기본 콜백 기능을 사용할 수는 없나요?
콜백 기능 문제
콜백 함수는 클릭을 기반으로 양식 유효성을 검사하거나 REST 호출 결과를 저장하는 등 단순 반복 이벤트에 적합합니다. 콜백 함수는 또한 하나의 콜백 함수가 REST 함수를 호출하고, REST 함수에 대한 새 콜백 함수를 설정하고, 이 새 콜백 함수가 다른 REST 함수를 호출하는 등의 코드 체인을 생성합니다. 코드의 수평적 성장은 수직적 성장보다 큽니다. 콜백 함수는 결과가 필요할 때까지 간단해 보이며 다음 줄 계산에 즉시 사용하기 위해 결과가 필요합니다.
'use strict'; var i = 0; function log(data) {console.log('%d %s', ++i, data); }; function validate() { log("Wait for it ..."); // Sequence of four Long-running async activities setTimeout(function () { log('result first'); setTimeout(function () { log('result second'); setTimeout(function () { log('result third'); setTimeout(function () { log('result fourth') }, 1000); }, 1000); }, 1000); }, 1000); }; validate();
비동기 작업을 시뮬레이션하기 위해 타임아웃을 사용합니다. 예외를 관리하는 방법은 고통스럽고 다운스트림 동작을 쉽게 이용할 수 있습니다. 콜백을 작성할 때 코드 구성이 혼란스러워집니다. 그림 2는 NodeJS REPL에서 실행할 수 있는 모의 유효성 검사 흐름을 보여줍니다. 다음 섹션에서는 파멸의 피라미드 패턴에서 지속적인 약속으로 이동해 보겠습니다.
그림
'use strict'; var i = 0; function log(data) {console.log('%d %s', ++i, data); }; // Asynchronous fn executes a callback result fn function async(arg, callBack) { setTimeout(function(){ log('result ' + arg); callBack(); }, 1000); }; function validate() { log("Wait for it ..."); // Sequence of four Long-running async activities async('first', function () { async('second',function () { async('third', function () { async('fourth', function () {}); }); }); }); }; validate();
NodeJS REPL 실행 결과
$ node scripts/examp2b.js 1 Wait for it ... 2 result first 3 result second 4 result third 5 result fourth $
저는 AngularJS 동적 유효성 검사가 해당 테이블의 값을 기반으로 양식 항목의 값을 동적으로 제한하는 상황에 직면한 적이 있습니다. 제한 항목의 유효한 값 범위는 REST 서비스에서 정의됩니다.
콜백 중첩을 피하기 위해 요청한 값에 따라 함수 스택을 작동하도록 스케줄러를 작성했습니다. 스케줄러는 스택에서 함수를 꺼내 실행합니다. 함수의 콜백은 스택이 지워질 때까지 마지막에 스케줄러를 다시 호출합니다. 각 콜백은 원격 유효성 검사 호출에서 반환된 모든 유효성 검사 오류를 기록합니다.
내가 쓴 글은 안티 패턴인 것 같아요. Angular의 $http 호출에서 제공되는 약속을 사용하면 전체 확인 프로세스 동안 내 생각은 동기 프로그래밍처럼 더 선형적이 될 것입니다. 평면화된 약속 체인을 읽을 수 있습니다. 계속...
Promise 사용
큐 약속 라이브러리가 사용됩니다. Q 라이브러리에도 동일하게 적용됩니다. 라이브러리를 사용하려면 먼저 npm을 사용하여 kew 라이브러리를 NodeJS로 가져온 다음 코드를 NodeJS REPL에 로드하세요.
그림
'use strict'; var Q = require('kew'); var i = 0; function log(data) {console.log('%d %s', ++i, data); }; // Asynchronous fn returns a promise function async(arg) { var deferred = Q.defer(); setTimeout(function () { deferred.resolve('result ' + arg);\ }, 1000); return deferred.promise; }; // Flattened promise chain function validate() { log("Wait for it ..."); async('first').then(function(resp){ log(resp); return async('second'); }) .then(function(resp){ log(resp); return async('third') }) .then(function(resp){ log(resp); return async('fourth'); }) .then(function(resp){ log(resp); }).fail(log); }; validate();
출력은 중첩된 콜백을 사용할 때와 동일합니다.
$ node scripts/examp2-pflat.js 1 Wait for it ... 2 result first 3 result second 4 result third 5 result fourth $
该代码稍微“长高”了,但我认为更易于理解和修改。更易于加上适当的错误处理。在链的末尾调用fail用于捕获链中错误,但我也可以在任何一个then里面提供一个reject的处理函数做相应的处理。
服务器 或 浏览器
Promises在浏览器中就像在NodeJS服务器中一样有效。下面的地址, http://jsfiddle.net/mauget/DnQDx/,指向JSFiddle的一个展示如何使用一个promise的web页面。 JSFiddle所有的代码是可修改的。我故意操作随意动作。你可以试几次得到相反的结果。它是可以直接扩展到多个promise链, 就像前面NodeJS例子。
并行 Promises
考虑一个异步操作喂养另一个异步操作。让后者包括三个并行异步行为,反过来,喂最后一个行动。只有当所有平行的子请求通过才能通过。这是灵感来自偶遇一打MongoDB操作。有些是合格的并行操作。我实现了promises的流流程图。
我们怎么会模拟那些在该图中心行的并行promises?关键是,最大的promise库有一个全功能,它产生一个包含一组子promises的父promie。当所有的子promises通过,父promise通过。如果有一个子promise拒绝,父promise拒绝。
让十个并行的promises每个都包含一个文字promise。只有当十个子类通过或如果任何子类拒绝,最后的then方法才能完成。
Figure
var promiseVals = ['To ', 'be, ', 'or ', 'not ', 'to ', 'be, ', 'that ', 'is ', 'the ', 'question.']; var startParallelActions = function (){ var promises = []; // Make an asynchronous action from each literal promiseVals.forEach(function(value){ promises.push(makeAPromise(value)); }); // Consolidate all promises into a promise of promises return Q.all(promises); }; startParallelActions ().then( . . .
下面的地址, http://jsfiddle.net/mauget/XKCy2/,针对JSFiddle在浏览器中运行十个并行promises,随机的拒绝或通过。这里有完整的代码用于检查和变化if条件。重新运行,直到你得到一个相反的完成。
孕育 Promise
许多api返回的promise都有一个then函数——他们是thenable。通常我只是通过then处理thenable函数的结果。然而,$q,mpromise,和kew库拥有同样的API用于创建,拒绝,或者通过promise。这里有API文档链接到每个库的引用部分。我通常不需要构造一个promise,除了本文中的包装promise的未知描述和timeout函数。请参考哪些我创建的promises。
Promise库互操作
大多数JavaScript promise库在then级别进行互操作。你可以从一个外部的promise创建一个promise,因为promise可以包装任何类型的值。then可以支持跨库工作。除了then,其他的promise函数则可能不同。如果你需要一个你的库不包含的函数,你可以将一个基于你的库的promise包装到一个新的,基于含有你所需函数的库创建的promise里面。例如,JQuery的promise有时为人所诟病。那么你可以将其包装到Q,$q,mpromise,或者kew库的promise中进行操作。
结语
现在我写了这篇文章,而一年前我却是犹豫要不要拥抱promise的那个。我只是单纯地想完成一项工作。 我不想学习新的API,或是打破我原来的代码(因为误解了promise)。我曾经如此错误地认为!当我下了一点注时,就轻易就赢得了可喜的成果。
在这篇文章中,我已经简单给出了一个单一的promise,promise链,和一个并行的promise的promise的的例子。 Promises不难使用。如果我可以使用它们,任何人都可以。 要查看完整的概念,我支持你点击专家写的参考指南。从Promises/A 的参考开始,从事实上的标准JavaScript的Promise 开始。