Có an toàn không khi giải quyết một lời hứa nhiều lần?


115

Tôi có một dịch vụ i18n trong ứng dụng của mình chứa mã sau:

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;
  };

Ý tưởng là người dùng sẽ gọi ensureLocaleIsLoadedvà đợi lời hứa được giải quyết. Nhưng do mục đích của hàm là chỉ đảm bảo rằng ngôn ngữ được tải, người dùng sẽ hoàn toàn ổn khi gọi nó nhiều lần.

Tôi hiện chỉ đang lưu trữ một lời hứa duy nhất và giải quyết nó nếu người dùng gọi lại hàm sau khi miền địa phương đã được truy xuất thành công từ máy chủ.

Từ những gì tôi có thể nói, điều này đang hoạt động như dự định, nhưng tôi tự hỏi liệu đây có phải là một cách tiếp cận phù hợp hay không.



Tôi cũng đã sử dụng nó và nó hoạt động tốt.
Chandermani

Câu trả lời:


119

Như tôi hiểu những lời hứa hiện tại, điều này sẽ ổn 100%. Chỉ có một điều cần hiểu là một khi được giải quyết (hoặc bị từ chối), đó là đối với một đối tượng bị khử - nó được thực hiện.

Nếu bạn nên gọi then(...) lại lời hứa, bạn sẽ nhận được ngay kết quả được giải quyết / từ chối (đầu tiên).

Các cuộc gọi bổ sung đến resolve()sẽ không (không nên?) Có bất kỳ ảnh hưởng nào. Không chắc chắn điều gì sẽ xảy ra nếu bạn cố gắng truy cập rejectmột đối tượng đã được định hướng trước đó resolved(tôi nghi ngờ là không có gì).


28
Đây là JSBin minh họa rằng tất cả những điều trên thực sự là đúng: jsbin.com/gemepay/3/edit?js,console Chỉ có giải pháp đầu tiên được sử dụng.
konrad

4
Có ai tìm thấy bất kỳ tài liệu chính thức về điều này? Nói chung là không thể dựa vào hành vi không có giấy tờ ngay cả khi nó hoạt động ngay bây giờ.
3ocen

ecma-international.org/ecma-262/6.0/#sec-promise.resolve - Cho đến nay, tôi không tìm thấy bất kỳ điều gì nói rằng nó vốn dĩ KHÔNG AN TOÀN. Nếu trình xử lý của bạn thực hiện điều gì đó thực sự chỉ nên được thực hiện MỘT LẦN, tôi sẽ yêu cầu trình xử lý kiểm tra và cập nhật một số trạng thái trước khi thực hiện lại hành động. Nhưng tôi cũng muốn một số mục nhập MDN chính thức hoặc tài liệu đặc tả để có được sự rõ ràng tuyệt đối.
demaniak

Tôi không thể thấy bất cứ điều gì "rắc rối" trong trang PromiseA +. Xem promisesaplus.com
demaniak

3
@demaniak Câu hỏi này là về Hứa hẹn / A + , không phải về lời hứa của ES6. Nhưng để trả lời câu hỏi của bạn, một phần của thông số kỹ thuật ES6 về giải quyết / từ chối không liên quan có an toàn ở đây .
Trevor Robinson

1

Tôi đã đối mặt với điều tương tự một thời gian trước, thực sự một lời hứa chỉ có thể được giải quyết một lần, những lần thử khác sẽ không làm gì cả (không có lỗi, không có cảnh báo, không có thenlời kêu gọi).

Tôi quyết định giải quyết nó như thế này:

getUsers(users => showThem(users));

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

chỉ cần chuyển hàm của bạn như một cuộc gọi lại và gọi nó bao nhiêu lần bạn muốn! Hy vọng điều đó có ý nghĩa.


Tôi nghĩ rằng điều này là sai. Bạn có thể chỉ cần gửi lại lời hứa getUsersvà sau đó gọi lại .then()lời hứa đó bao nhiêu lần tùy thích. Không cần phải chuyển một cuộc gọi lại. Theo ý kiến ​​của tôi, một trong những ưu điểm của hứa hẹn là bạn không cần chỉ định gọi lại trước.
John Henckel

@JohnHenckel Ý tưởng là giải quyết lời hứa nhiều lần, tức là trả về dữ liệu nhiều lần, không có nhiều .thencâu lệnh. Đối với những gì nó đáng giá, tôi nghĩ rằng cách duy nhất để trả lại dữ liệu nhiều lần cho ngữ cảnh gọi là sử dụng các lệnh gọi lại chứ không phải các lời hứa, vì các lời hứa không được xây dựng để hoạt động theo cách đó.
T. Rex

0

Nếu bạn cần thay đổi giá trị trả lại của lời hứa, chỉ cần trả lại giá trị mới trong thenvà chuỗi tiếp theo then/ catchtrên đó

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

Không có cách rõ ràng nào để giải quyết các lời hứa nhiều lần bởi vì nó đã được giải quyết xong. Cách tiếp cận tốt hơn ở đây là sử dụng mẫu có thể quan sát được, ví dụ như tôi đã viết đoạn mã sau để quan sát sự kiện máy khách socket. Bạn có thể mở rộng mã này để đáp ứng nhu cầu của bạn

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

Bạn có thể viết các bài kiểm tra để xác nhận hành vi.

Bằng cách chạy thử nghiệm sau, bạn có thể kết luận rằng

Lời gọi quyết định () / từ chối () không bao giờ ném lỗi.

Sau khi được giải quyết (bị từ chối), giá trị đã giải quyết (lỗi bị từ chối) sẽ được giữ nguyên bất kể lệnh gọi giải quyết () hoặc từ chối () sau.

Bạn cũng có thể kiểm tra bài đăng trên blog của tôi để biết chi tiết.

/* 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

Những gì bạn nên làm là đặt một ng-if trên ng-outlet chính của mình và thay vào đó hãy hiển thị một vòng quay tải. Khi ngôn ngữ của bạn được tải, bạn sẽ hiển thị ổ cắm và để hệ thống phân cấp thành phần hiển thị. Bằng cách này, tất cả ứng dụng của bạn có thể giả định rằng ngôn ngữ đã được tải và không cần kiểm tra.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.