약속을 여러 번 해결하는 것이 안전합니까?


115

내 응용 프로그램에 다음 코드가 포함 된 i18n 서비스가 있습니다.

var i18nService = function() {
  this.ensureLocaleIsLoaded = function() {
    if( !this.existingPromise ) {
      this.existingPromise = $q.defer();

      var deferred = this.existingPromise;
      var userLanguage = $( "body" ).data( "language" );
      this.userLanguage = userLanguage;

      console.log( "Loading locale '" + userLanguage + "' from server..." );
      $http( { method:"get", url:"/i18n/" + userLanguage, cache:true } ).success( function( translations ) {
        $rootScope.i18n = translations;
        deferred.resolve( $rootScope.i18n );
      } );
    }

    if( $rootScope.i18n ) {
      this.existingPromise.resolve( $rootScope.i18n );
    }

    return this.existingPromise.promise;
  };

아이디어는 사용자가 전화를 걸어 ensureLocaleIsLoaded약속이 해결 될 때까지 기다릴 것이라는 것 입니다. 그러나 함수의 목적이 로케일이로드 되었는지 확인 하는 것이므로 사용자가 여러 번 호출하는 것이 완벽합니다.

저는 현재 단일 promise를 저장하고 서버에서 로케일이 성공적으로 검색된 후 사용자가 함수를 다시 호출하면 해결합니다.

내가 알 수 있듯이 이것은 의도 한대로 작동하지만 이것이 적절한 접근 방식인지 궁금합니다.


7
이 답변을 참조하십시오 .
robertklep 2013-12-02

나도 그것을 사용했고 잘 작동합니다.
Chandermani 2013

답변:


119

현재 약속을 이해하고 있으므로 100 % 괜찮습니다. 이해해야 할 것은 일단 해결 (또는 거부)되면 지연된 객체에 대한 것입니다.

then(...)약속을 다시 요청해야한다면 즉시 (첫 번째) 해결 / 거부 결과를 받아야합니다.

에 대한 추가 호출은 효과 resolve()가 없습니다 (안돼야합니까?). reject이전에 지연된 개체를 시도하면 어떻게되는지 확실하지 않습니다 resolved(아무것도 의심하지 않습니다).


28
다음은 위의 모든 것이 사실임을 보여주는 JSBin입니다. jsbin.com/gemepay/3/edit?js,console 첫 번째 해결 방법 만 사용됩니다.
konrad

4
누구든지 이것에 대한 공식 문서를 찾았습니까? 지금 당장 작동하더라도 문서화되지 않은 동작에 의존하는 것은 일반적으로 바람직하지 않습니다.
3ocene 2018 년

ecma-international.org/ecma-262/6.0/#sec-promise.resolve- 본질적으로 안전하지 않다는 내용을 지금까지 발견하지 못했습니다. 핸들러가 실제로 한 번만 수행해야하는 작업을 수행하는 경우 작업을 다시 수행하기 전에 일부 상태를 확인하고 업데이트하도록합니다. 그러나 나는 또한 절대적인 명확성을 얻기 위해 공식 MDN 항목이나 사양 문서를 원합니다.
demaniak

PromiseA + 페이지에서 "문제"를 볼 수 없습니다. promisesaplus.com
demaniak

3
@demaniak이 질문은 ES6 약속이 아니라 Promises / A + 에 관한 것 입니다. 그러나 귀하의 질문에 답하기 위해 외부 해결 / 거부 안전에 대한 ES6 사양의 일부는 여기에 있습니다 .
Trevor Robinson

1

나는 얼마 전에 똑같은 일에 직면했습니다. 실제로 약속은 한 번만 해결 될 수 있으며 다른 시도는 아무 일도하지 않습니다 (오류 없음, 경고 없음, then호출 없음 ).

다음과 같이 해결하기로 결정했습니다.

getUsers(users => showThem(users));

getUsers(callback){
    callback(getCachedUsers())
    api.getUsers().then(users => callback(users))
}

함수를 콜백으로 전달하고 원하는만큼 호출하면됩니다! 이해가 되길 바랍니다.


나는 이것이 잘못된 것 같다. 약속을 반환 getUsers한 다음 .then()원하는 횟수만큼 해당 약속 을 호출 할 수 있습니다. 콜백을 전달할 필요가 없습니다. 제 생각에 promise의 장점 중 하나는 콜백을 미리 지정할 필요가 없다는 것입니다.
John Henckel

@JohnHenckel 아이디어는 약속을 여러 번 해결하는 것입니다. 즉, 데이터를 여러 번 반환하고 여러 .then명령문이 없습니다 . 그만한 가치가 있기 때문에, 호출 컨텍스트에 데이터를 여러 번 반환하는 유일한 방법은 약속이 그런 방식으로 작동하도록 만들어지지 않았기 때문에 약속이 아닌 콜백을 사용하는 것입니다.
T. Rex

0

promise의 반환 값을 변경해야하는 경우 새 값을 반환 then하고 다음 then/ 연결 catch하면됩니다.

var p1 = new Promise((resolve, reject) => { resolve(1) });
    
var p2 = p1.then(v => {
  console.log("First then, value is", v);
  return 2;
});
    
p2.then(v => {
  console.log("Second then, value is", v);
});


0

약속이 해결 되었기 때문에 여러 번 해결하는 명확한 방법이 없습니다. 여기서 더 나은 접근 방식은 관찰자 관찰 가능 패턴을 사용하는 것입니다. 예를 들어 소켓 클라이언트 이벤트를 관찰하는 다음 코드를 작성했습니다. 필요에 맞게이 코드를 확장 할 수 있습니다.

const evokeObjectMethodWithArgs = (methodName, args) => (src) => src[methodName].apply(null, args);
    const hasMethodName = (name) => (target = {}) => typeof target[name] === 'function';
    const Observable = function (fn) {
        const subscribers = [];
        this.subscribe = subscribers.push.bind(subscribers);
        const observer = {
            next: (...args) => subscribers.filter(hasMethodName('next')).forEach(evokeObjectMethodWithArgs('next', args))
        };
        setTimeout(() => {
            try {
                fn(observer);
            } catch (e) {
                subscribers.filter(hasMethodName('error')).forEach(evokeObjectMethodWithArgs('error', e));
            }
        });

    };

    const fromEvent = (target, eventName) => new Observable((obs) => target.on(eventName, obs.next));

    fromEvent(client, 'document:save').subscribe({
        async next(document, docName) {
            await writeFilePromise(resolve(dataDir, `${docName}`), document);
            client.emit('document:save', document);
        }
    });

0

동작을 확인하는 테스트를 작성할 수 있습니다.

다음 테스트를 실행하면 다음과 같은 결론을 내릴 수 있습니다.

resolve () / reject () 호출은 오류를 발생시키지 않습니다.

일단 정산 (거부)되면 다음 resolve () 또는 reject () 호출에 관계없이 해결 된 값 (거부 된 오류)이 유지됩니다.

자세한 내용은 내 블로그 게시물 을 확인할 수도 있습니다 .

/* eslint-disable prefer-promise-reject-errors */
const flipPromise = require('flip-promise').default

describe('promise', () => {
    test('error catch with resolve', () => new Promise(async (rs, rj) => {
        const getPromise = () => new Promise(resolve => {
            try {
                resolve()
            } catch (err) {
                rj('error caught in unexpected location')
            }
        })
        try {
            await getPromise()
            throw new Error('error thrown out side')
        } catch (e) {
            rs('error caught in expected location')
        }
    }))
    test('error catch with reject', () => new Promise(async (rs, rj) => {
        const getPromise = () => new Promise((_resolve, reject) => {
            try {
                reject()
            } catch (err) {
                rj('error caught in unexpected location')
            }
        })
        try {
            await getPromise()
        } catch (e) {
            try {
                throw new Error('error thrown out side')
            } catch (e){
                rs('error caught in expected location')
            }
        }
    }))
    test('await multiple times resolved promise', async () => {
        const pr = Promise.resolve(1)
        expect(await pr).toBe(1)
        expect(await pr).toBe(1)
    })
    test('await multiple times rejected promise', async () => {
        const pr = Promise.reject(1)
        expect(await flipPromise(pr)).toBe(1)
        expect(await flipPromise(pr)).toBe(1)
    })
    test('resolve multiple times', async () => {
        const pr = new Promise(resolve => {
            resolve(1)
            resolve(2)
            resolve(3)
        })
        expect(await pr).toBe(1)
    })
    test('resolve then reject', async () => {
        const pr = new Promise((resolve, reject) => {
            resolve(1)
            resolve(2)
            resolve(3)
            reject(4)
        })
        expect(await pr).toBe(1)
    })
    test('reject multiple times', async () => {
        const pr = new Promise((_resolve, reject) => {
            reject(1)
            reject(2)
            reject(3)
        })
        expect(await flipPromise(pr)).toBe(1)
    })

    test('reject then resolve', async () => {
        const pr = new Promise((resolve, reject) => {
            reject(1)
            reject(2)
            reject(3)
            resolve(4)
        })
        expect(await flipPromise(pr)).toBe(1)
    })
test('constructor is not async', async () => {
    let val
    let val1
    const pr = new Promise(resolve => {
        val = 1
        setTimeout(() => {
            resolve()
            val1 = 2
        })
    })
    expect(val).toBe(1)
    expect(val1).toBeUndefined()
    await pr
    expect(val).toBe(1)
    expect(val1).toBe(2)
})

})

-1

당신이해야 할 일은 메인 ng-outlet에 ng-if를 넣고 대신 로딩 스피너를 보여주는 것입니다. 로케일이로드되면 출력이 표시되고 구성 요소 계층이 렌더링됩니다. 이렇게하면 모든 애플리케이션에서 로케일이로드되고 확인이 필요하지 않다고 가정 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.