Söz zincirini kırın ve zincirdeki kırıldığı (reddedildiği) adıma dayanan bir işlev çağırın


135

Güncelleme:

Bu yazının gelecekteki izleyicilerine yardımcı olmak için bu pluma cevabının demosunu oluşturdum .

Soru:

Hedefim oldukça açık görünüyor.

  step(1)
  .then(function() {
    return step(2);
  }, function() {
    stepError(1);
    return $q.reject();
  })
  .then(function() {

  }, function() {
    stepError(2);
  });

  function step(n) {
    var deferred = $q.defer();
    //fail on step 1
    (n === 1) ? deferred.reject() : deferred.resolve();
    return deferred.promise;
  }
  function stepError(n) {
    console.log(n); 
  }

Buradaki sorun, 1. adımda başarısız olursam, her iki stepError(1)AND'in stepError(2)de tetiklenmesidir. Eğer return $q.rejecto stepError(2)zaman kovulmayacağım, ama step(2)anlayacağım. Yapmaya çalıştığım şeyler dışında her şeyi başardım.

Hata zincirindeki tüm işlevleri çağırmadan reddedilme işlevini çağırabilmem için nasıl söz verebilirim? Yoksa bunu başarmanın başka bir yolu var mı?

İşte canlı demo Birlikte işe yarayacak bir şey var bu yüzden.

Güncelleme:

Ben biraz bunu çözmüş. Burada, zincirin sonunda hata yakalamak ve veri iletmek reject(data)böylece ben hata fonksiyonu ele almak için hangi sorunu bilecek. Verilere bağlı kalmak istemediğim için bu aslında gereksinimlerimi karşılamıyor. Topal olurdu, ancak benim durumumda, ne yapılacağını belirlemek için döndürülen verilere bağlı kalmak yerine işleve bir hata geri çağrısı iletmek daha temiz olurdu.

Burada canlı demo (tıklayın).

step(1)
  .then(function() {
    return step(2);
  })
  .then(function() {
    return step(3);
  })
  .then(false, 
    function(x) {
      stepError(x);
    }
  );
  function step(n) {
    console.log('Step '+n);
    var deferred = $q.defer();
    (n === 1) ? deferred.reject(n) : deferred.resolve(n);
    return deferred.promise;
  }
  function stepError(n) {
    console.log('Error '+n); 
  }

1
Bu daha karmaşık hale gelirse yardımcı olabilecek bir async javascript lib var
lucuma

Promise.prototype.catch()MDN örnekleri aynı sorunların çözümünü göstermektedir.
toraritte

Yanıtlar:


199

Kodunuzun beklendiği gibi çalışmamasının nedeni, aslında sandığınızdan farklı bir şey yapmasıdır.

Diyelim ki aşağıdakine benzer bir şeyiniz var:

stepOne()
.then(stepTwo, handleErrorOne)
.then(stepThree, handleErrorTwo)
.then(null, handleErrorThree);

Neler olduğunu daha iyi anlamak için, bunun try/ catchblokları ile senkronize kod olduğunu varsayalım:

try {
    try {
        try {
            var a = stepOne();
        } catch(e1) {
            a = handleErrorOne(e1);
        }
        var b = stepTwo(a);
    } catch(e2) {
        b = handleErrorTwo(e2);
    }
    var c = stepThree(b);
} catch(e3) {
    c = handleErrorThree(e3);
}

onRejectedİşleyici (ikinci argümanı then) esas olarak, (a gibi bir hata düzeltme mekanizması catchblok). Bir hata atılırsa handleErrorOne, bir sonraki catch bloğu ( catch(e2)) tarafından yakalanır vb.

Açıkçası bu sizin istediğiniz gibi değil.

Diyelim ki sorun ne olursa olsun tüm çözünürlük zincirinin başarısız olmasını istiyoruz:

stepOne()
.then(function(a) {
    return stepTwo(a).then(null, handleErrorTwo);
}, handleErrorOne)
.then(function(b) {
    return stepThree(b).then(null, handleErrorThree);
});

Not: Olduğu handleErrorOneyeri bırakabiliriz , çünkü yalnızca stepOnereddedilirse çağrılır (zincirdeki ilk işlevdir, bu nedenle zincir bu noktada reddedilirse, yalnızca bu işlevin vaadinden dolayı olabilir) .

Önemli değişiklik, diğer işlevlerin hata işleyicilerinin ana söz zincirinin bir parçası olmamasıdır. Bunun yerine, her adımın kendi "alt zinciri" vardır ve onRejectedbu adım yalnızca adım reddedilirse çağrılır (ancak ana zincir tarafından doğrudan ulaşılamazsa).

Bunun nedeni hem onFulfilledve hem onRejectedde thenyöntemin isteğe bağlı argümanlar olmasıdır . Eğer bir vaat yerine getirilirse (yani çözülürse) ve thenzincirdeki bir sonrakinde bir onFulfilledişleyici yoksa , zincir böyle bir işleyiciye sahip olana kadar devam eder.

Bu, aşağıdaki iki satırın eşdeğer olduğu anlamına gelir:

stepOne().then(stepTwo, handleErrorOne)
stepOne().then(null, handleErrorOne).then(stepTwo)

Ancak aşağıdaki satır yukarıdaki ikiye eşdeğer değildir :

stepOne().then(stepTwo).then(null, handleErrorOne)

$qAngular'ın vaat kütüphanesi kriskowal'ın Q(daha zengin bir API'ye sahip, ancak bulabileceğiniz her şeyi içeren) kütüphanesine dayanıyor $q. Q'nun GitHub'daki API dokümanları yararlı olabilir. Q , vaat çözümleme davranışının tam olarak nasıl ve nasıl çalıştığına dair ayrıntılara giren Promises / A + spesifikasyonunu uygular then.

DÜZENLE:

Ayrıca, hata işleyicinizdeki zincirden çıkmak istiyorsanız, reddedilen bir söz vermesi veya bir Hata atması gerektiğini unutmayın (bu, otomatik olarak reddedilen ve reddedilen bir söze sarılacaktır). Bir söz vermezseniz then, dönüş değerini sizin için bir çözüm sözüne sarar.

Bu, herhangi bir şey döndürmezseniz, değer için etkili bir çözüm sözü verdiğiniz anlamına gelir undefined.


138
Bu bölüm altın: if you don't return anything, you are effectively returning a resolved promise for the value undefined.Teşekkürler @pluma
Valerio

7
Bu gerçekten. Bunu hak ettiği
cesareti

reddetme geçerli işlevden çıkar mı? örneğin, reddetme 1. `denirse (kötü) {reddet (durum); } çözmek (sonuç); ``
SuperUberDuper

stepOne().then(stepTwo, handleErrorOne) `stepOne (). then (null, handleErrorOne) .then (stepTwo)` Bunlar tamamen eşdeğer mi? Ben stepOneikinci kod satırında reddetme durumunda yürütmek stepTwoama ilk sadece yürütmek handleErrorOneve durdurmak düşünüyorum düşünüyorum . Yoksa bir şey mi kaçırıyorum?
JeFf

5
Yine de sorulan soru için net bir çözüm sağlamaz, yine de iyi bir açıklama
Yerken

57

Partiye biraz geç ama bu basit çözüm benim için çalıştı:

function chainError(err) {
  return Promise.reject(err)
};

stepOne()
.then(stepTwo, chainError)
.then(stepThreee, chainError);

Bu olanak kırmak zincirinin dışına.


1
Bana yardımcı oldu ama FYI, o zaman yakalamak gibi dışarı çıkmak için o zaman iade edebilirsiniz:.then(user => { if (user) return Promise.reject('The email address already exists.') })
Craig van Tonder

1
@CraigvanTonder Bir sözün içine atabilirsin ve seninkiyle aynı şekilde çalışır:.then(user => { if (user) throw 'The email address already exists.' })
Francisco Presencia

1
Tek doğru cevap bu. Aksi takdirde, 3. adımda hata olsa bile 3. adım yürütülür.
wdetac

1
Sadece açıklığa kavuşturmak için, stepOne () öğesinde bir hata oluşursa, her iki chainError doğru şekilde çağrılır mı? Bu arzu edilirse. Bunu yapan bir pasajım var, bir
user320550

10

İhtiyacınız olan şey .then(), başlamak için özel bir kasa ve bitirmek için özel bir kasa içeren tekrarlanan bir zincirdir.

Zorluk, son bir hata işleyicisine dalmak için başarısızlık durumunun adım numarasını elde etmektir.

  • Başlat: step(1)koşulsuz olarak arayın .
  • Yinelenen desen: a .then()aşağıdaki çağrılarla zincir a :
    • başarı: çağrı adımı (n + 1)
    • başarısızlık: bir önceki ertelenen değeri reddettiğiniz değeri atın veya hatayı yeniden alın.
  • Bitiş: .then()başarı işleyicisi ve son hata işleyicisi olmayan zincir a .

Her şeyi uzun süre yazabilirsiniz, ancak adlandırılmış, genelleştirilmiş işlevlerle kalıbı göstermek daha kolaydır:

function nextStep(n) {
    return step(n + 1);
}

function step(n) {
    console.log('step ' + n);
    var deferred = $q.defer();
    (n === 3) ? deferred.reject(n) : deferred.resolve(n);
    return deferred.promise;
}

function stepError(n) {
    throw(n);
}

function finalError(n) {
    console.log('finalError ' + n);
}
step(1)
    .then(nextStep, stepError)
    .then(nextStep, stepError)
    .then(nextStep, stepError)
    .then(nextStep, stepError)
    .then(nextStep, stepError)
    .then(null, finalError);});

bkz demo

Ertelemenin nasıl step()reddedildiğine veya çözüldüğüne dikkat edin n, böylece bu değeri .then()zincirde bir sonraki geri aramalar için kullanılabilir hale getirin . Bir kez stepErrorçağrıldığında, hata, ele alınana kadar tekrar tekrar döndürülür finalError.


Bilgilendirici cevap, bu yüzden tutmaya değer, ancak karşılaştığım sorun bu değil. Bu çözümü yazıma aktardım ve aradığım şey bu değil. Gönderinin üst kısmındaki demoya bakın.
m59

1
m59, bu, "hata zincirindeki tüm işlevleri çağırmadan reddedilme işlevini çağırabilmem için nasıl söz verebilirim?" ve "Promise zincirini kırın ve zincirdeki kırıldığı (reddedildiği) adıma göre bir fonksiyon çağırın" sorusunun başlığı
Pancar-Beetroot

Dediğim gibi, bilgilendirici ve bu çözümü yazıma ekledim (daha az ayrıntıyla). Bu yaklaşım, zincirin devam edebilmesi için bir şeyleri düzeltmek için tasarlanmıştır. O iken olabilir ben aradığım başarmak, bu kabul cevap yaklaşımı gibi doğal değil. Başka bir deyişle, başlık ve sorulan soru ile ifade edilen şeyi yapmak istiyorsanız, pluma'nın yaklaşımını kullanın.
m59

7

Reddetme sırasında bir reddetme hatası iletmelisiniz, ardından adım hatası işleyicilerini reddetmenin işlenip işlenmediğini veya zincirin sonuna kadar "yeniden" döndürülmesini kontrol eden bir işleve sarın:

// function mocking steps
function step(i) {
    i++;
    console.log('step', i);
    return q.resolve(i);
}

// function mocking a failing step
function failingStep(i) {
    i++;
    console.log('step '+ i + ' (will fail)');
    var e = new Error('Failed on step ' + i);
    e.step = i;
    return q.reject(e);
}

// error handler
function handleError(e){
    if (error.breakChain) {
        // handleError has already been called on this error
        // (see code bellow)
        log('errorHandler: skip handling');
        return q.reject(error);
    }
    // firs time this error is past to the handler
    console.error('errorHandler: caught error ' + error.message);
    // process the error 
    // ...
    //
    error.breakChain = true;
    return q.reject(error);
}

// run the steps, will fail on step 4
// and not run step 5 and 6
// note that handleError of step 5 will be called
// but since we use that error.breakChain boolean
// no processing will happen and the error will
// continue through the rejection path until done(,)

  step(0) // 1
  .catch(handleError)
  .then(step) // 2
  .catch(handleError)
  .then(step) // 3
  .catch(handleError)
  .then(failingStep)  // 4 fail
  .catch(handleError)
  .then(step) // 5
  .catch(handleError)
  .then(step) // 6
  .catch(handleError)
  .done(function(){
      log('success arguments', arguments);
  }, function (error) {
      log('Done, chain broke at step ' + error.step);
  });

Konsolda gördükleriniz:

step 1
step 2
step 3
step 4 (will fail)
errorHandler: caught error 'Failed on step 4'
errorHandler: skip handling
errorHandler: skip handling
Done, chain broke at step 4

İşte bazı çalışma kodu https://jsfiddle.net/8hzg5s7m/3/

Her adım için belirli bir işleminiz varsa, paketiniz aşağıdaki gibi olabilir:

/*
 * simple wrapper to check if rejection
 * has already been handled
 * @param function real error handler
 */
function createHandler(realHandler) {
    return function(error) {
        if (error.breakChain) {
            return q.reject(error);
        }
        realHandler(error);
        error.breakChain = true;
        return q.reject(error);    
    }
}

o zaman zincirin

step1()
.catch(createHandler(handleError1Fn))
.then(step2)
.catch(createHandler(handleError2Fn))
.then(step3)
.catch(createHandler(handleError3Fn))
.done(function(){
    log('success');
}, function (error) {
    log('Done, chain broke at step ' + error.step);
});

2

Doğru anlarsam, yalnızca başarısız olan adımın gösterilmesini istemeniz gerekir, değil mi?

Bu, ilk vaadin başarısızlık durumunu değiştirmek kadar basit olmalıdır:

step(1).then(function (response) {
    step(2);
}, function (response) {
    stepError(1);
    return response;
}).then( ... )

$q.reject()İlk adımın başarısızlık durumuna geri dönerek , bu sözü reddedersiniz, bu da errorCallback'in 2.'de çağrılmasına neden olur then(...).


Dünyada ne ... tam olarak bunu yaptım! Mesajımda bunu denediğimi görün, ama zincir geri çekilip koşacaktı step(2). Şimdi tekrar denedim. Kafam çok karışık.
m59

1
Bundan bahsettiğini gördüm. Yine de bu tuhaf. Bu işlev return step(2);yalnızca step(1)başarıyla çözüldüğünde çağrılmalıdır .
Zajn

Çizik - kesinlikle oluyor. Benim yazımda söylediğim gibi, eğer kullanmazsan return $q.reject(), zincir devam edecek. Bu durumda return responseberbat ettim. Şuna
m59

Hmm tamam. Bunu değiştirdiğimde yayınladığınız jsbin'de çalışıyor gibi görünüyor, ama bir şey kaçırmış olmalıyım.
Zajn

Evet, kesinlikle işe yaramıyor. Benim için çizim tahtasına geri dön!
Zajn

2
var s = 1;
start()
.then(function(){
    return step(s++);
})
.then(function() {
    return step(s++);
})
.then(function() {
    return step(s++);
})
.then(0, function(e){
   console.log(s-1); 
});

http://jsbin.com/EpaZIsIp/20/edit

Veya herhangi bir adım için otomatikleştirilir:

var promise = start();
var s = 1;
var l = 3;
while(l--) {
    promise = promise.then(function() {
        return step(s++);
    });
}
promise.then(0, function(e){
   console.log(s-1); 
});

http://jsbin.com/EpaZIsIp/21/edit


Ama arayacaksam deferred.reject(n)o zaman söz veriyorum bir hata dışı nesne ile reddedildi uyarı alıyorum
9me

2

Bunu libs gibi kullanın:

https://www.npmjs.com/package/promise-chain-break

    db.getData()
.then(pb((data) => {
    if (!data.someCheck()) {
        tellSomeone();

        // All other '.then' calls will be skiped
        return pb.BREAK;
    }
}))
.then(pb(() => {
}))
.then(pb(() => {
}))
.catch((error) => {
    console.error(error);
});

2

Bu sorunu async / await kullanarak çözmek istiyorsanız:

(async function(){    
    try {        
        const response1, response2, response3
        response1 = await promise1()

        if(response1){
            response2 = await promise2()
        }
        if(response2){
            response3 = await promise3()
        }
        return [response1, response2, response3]
    } catch (error) {
        return []
    }

})()

1

Hata işleyicilerini doğrudan adımların yürütülmesine ayrı zincir öğeleri olarak ekleyin:

        // Handle errors for step(1)
step(1).then(null, function() { stepError(1); return $q.reject(); })
.then(function() {
                 // Attach error handler for step(2),
                 // but only if step(2) is actually executed
  return step(2).then(null, function() { stepError(2); return $q.reject(); });
})
.then(function() {
                 // Attach error handler for step(3),
                 // but only if step(3) is actually executed
  return step(3).then(null, function() { stepError(3); return $q.reject(); });
});

veya kullanarak catch():

       // Handle errors for step(1)
step(1).catch(function() { stepError(1); return $q.reject(); })
.then(function() {
                 // Attach error handler for step(2),
                 // but only if step(2) is actually executed
  return step(2).catch(function() { stepError(2); return $q.reject(); });
})
.then(function() {
                 // Attach error handler for step(3),
                 // but only if step(3) is actually executed
  return step(3).catch(function() { stepError(3); return $q.reject(); });
});

Not: Bu temel olarak pluma'nın cevabında önerdiği aynı kalıptır, ancak OP'nin ismini kullanır.


1

Bulundu Promise.prototype.catch() MDN'yi üzerinde örnekler çok yararlı aşağıda.

(Kabul edilen cevaptan bahsediliyor then(null, onErrorHandler) temel olarak aynıdır catch(onErrorHandler).)

Yakalama yöntemini kullanma ve zincirleme

var p1 = new Promise(function(resolve, reject) {
  resolve('Success');
});

p1.then(function(value) {
  console.log(value); // "Success!"
  throw 'oh, no!';
}).catch(function(e) {
  console.log(e); // "oh, no!"
}).then(function(){
  console.log('after a catch the chain is restored');
}, function () {
  console.log('Not fired due to the catch');
});

// The following behaves the same as above
p1.then(function(value) {
  console.log(value); // "Success!"
  return Promise.reject('oh, no!');
}).catch(function(e) {
  console.log(e); // "oh, no!"
}).then(function(){
  console.log('after a catch the chain is restored');
}, function () {
  console.log('Not fired due to the catch');
});

Hataları atarken yakaladım

// Throwing an error will call the catch method most of the time
var p1 = new Promise(function(resolve, reject) {
  throw 'Uh-oh!';
});

p1.catch(function(e) {
  console.log(e); // "Uh-oh!"
});

// Errors thrown inside asynchronous functions will act like uncaught errors
var p2 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    throw 'Uncaught Exception!';
  }, 1000);
});

p2.catch(function(e) {
  console.log(e); // This is never called
});

// Errors thrown after resolve is called will be silenced
var p3 = new Promise(function(resolve, reject) {
  resolve();
  throw 'Silenced Exception!';
});

p3.catch(function(e) {
   console.log(e); // This is never called
});

Çözülmüşse

//Create a promise which would not call onReject
var p1 = Promise.resolve("calling next");

var p2 = p1.catch(function (reason) {
    //This is never called
    console.log("catch p1!");
    console.log(reason);
});

p2.then(function (value) {
    console.log("next promise's onFulfilled"); /* next promise's onFulfilled */
    console.log(value); /* calling next */
}, function (reason) {
    console.log("next promise's onRejected");
    console.log(reason);
});

1

En iyi çözüm, ES6 beklemesini kullanmak için söz zincirinize yeniden odaklanmaktır. Ardından, davranışın geri kalanını atlamak için işlevden dönebilirsiniz.

Bir yıldan fazla bir süredir kafamı bu kalıba vuruyorum ve beklemek cennettir.


Saf IE kullanırken async / await desteklenmez.
ndee

0

SequentialPromise Modülü Kullanma

niyet

Sorumluluğu, her işlemin geçerli dizinini sıralı olarak izlerken, istekleri sırayla yürütmek olan bir modül sağlayın. Esneklik için bir Komut Deseni'nde işlemi tanımlayın .

Katılımcılar

  • Bağlam : Üye yöntemi bir işlem gerçekleştiren nesne.
  • SequentialPromise : executeHer işlemi zincirlemek ve izlemek için bir yöntem tanımlar . SequentialPromise gerçekleştirilen tüm işlemlerden bir Promise-Chain döndürür.
  • Invoker : Bağlam ve eylem sağlayan bir SequentialPromise örneği oluşturur ve executeher işlem için sıralı bir seçenekler listesinden geçerken yöntemini çağırır .

Sonuçlar

Promise çözünürlüğünün sıralı davranışı gerektiğinde SequentialPromise kullanın. SequentialPromise, bir Sözün reddedildiği dizini izler.

uygulama

clear();

var http = {
    get(url) {
        var delay = Math.floor( Math.random() * 10 ), even = !(delay % 2);
        var xhr = new Promise(exe);

        console.log(`REQUEST`, url, delay);
        xhr.then( (data) => console.log(`SUCCESS: `, data) ).catch( (data) => console.log(`FAILURE: `, data) );

        function exe(resolve, reject) {
            var action = { 'true': reject, 'false': resolve }[ even ];
            setTimeout( () => action({ url, delay }), (1000 * delay) );
        }

        return xhr;
    }
};

var SequentialPromise = new (function SequentialPromise() {
    var PRIVATE = this;

    return class SequentialPromise {

        constructor(context, action) {
            this.index = 0;
            this.requests = [ ];
            this.context = context;
            this.action = action;

            return this;
        }

        log() {}

        execute(url, ...more) {
            var { context, action, requests } = this;
            var chain = context[action](url);

            requests.push(chain);
            chain.then( (data) => this.index += 1 );

            if (more.length) return chain.then( () => this.execute(...more) );
            return chain;
        }

    };
})();

var sequence = new SequentialPromise(http, 'get');
var urls = [
    'url/name/space/0',
    'url/name/space/1',
    'url/name/space/2',
    'url/name/space/3',
    'url/name/space/4',
    'url/name/space/5',
    'url/name/space/6',
    'url/name/space/7',
    'url/name/space/8',
    'url/name/space/9'
];
var chain = sequence.execute(...urls);
var promises = sequence.requests;

chain.catch( () => console.warn(`EXECUTION STOPPED at ${sequence.index} for ${urls[sequence.index]}`) );

// console.log('>', chain, promises);

öz

SequentialPromise


0

Eğer herhangi bir noktada geri dönerseniz Promise.reject('something'), sözlüğe catch bloğuna atılırsınız.

promiseOne
  .then((result) => {
    if (!result) {
      return Promise.reject('No result');
    }
    return;
  })
  .catch((err) => {
    console.log(err);
  });

İlk vaat herhangi bir sonuç getirmezse , konsolda sadece 'Sonuç yok' elde edersiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.