Değişkenim bir işlevin içinde değiştirdikten sonra neden değişmiyor? - Eşzamansız kod referansı

May 15 2014

Aşağıdaki örnekler göz önüne alındığında, neden outerScopeVarher durumda tanımsızdır?

var outerScopeVar;

var img = document.createElement('img');
img.onload = function() {
    outerScopeVar = this.width;
};
img.src = 'lolcat.png';
alert(outerScopeVar);

var outerScopeVar;
setTimeout(function() {
    outerScopeVar = 'Hello Asynchronous World!';
}, 0);
alert(outerScopeVar);

// Example using some jQuery
var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
});
alert(outerScopeVar);

// Node.js example
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
    outerScopeVar = data;
});
console.log(outerScopeVar);

// with promises
var outerScopeVar;
myPromise.then(function (response) {
    outerScopeVar = response;
});
console.log(outerScopeVar);

// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
    outerScopeVar = pos;
});
console.log(outerScopeVar);

Neden undefinedtüm bu örneklerde çıktı veriyor ? Geçici çözümler istemiyorum, bunun neden olduğunu bilmek istiyorum .


Not: Bu, JavaScript eşzamansızlığı için kanonik bir sorudur . Bu soruyu geliştirmekten ve topluluğun özdeşleşebileceği daha basitleştirilmiş örnekler eklemekten çekinmeyin.

Yanıtlar

605 FabrícioMatté May 15 2014 at 06:55

Tek kelimelik cevap: eşzamansızlık .

Önsözler

Bu konu, burada Stack Overflow'da en az birkaç bin kez yinelenmiştir. Bu nedenle, ilk olarak son derece yararlı bazı kaynaklara işaret etmek istiyorum:


Eldeki sorunun cevabı

Önce ortak davranışı izleyelim. Tüm örneklerde, outerScopeVarbir işlevin içinde değiştirilir . Bu işlev açıkça hemen çalıştırılmıyor, bir bağımsız değişken olarak atanıyor veya aktarılıyor. Biz buna geri arama diyoruz .

Şimdi soru şu, bu geri arama ne zaman aranıyor?

Davaya bağlıdır. Tekrar bazı yaygın davranışları izlemeye çalışalım:

  • img.onloadgörüntünün başarıyla yüklendiğinde (ve eğer) gelecekte bir ara çağrılabilir .
  • setTimeoutGecikme süresi dolduktan ve zaman aşımı tarafından iptal edilmeden ileride çağrılabilir clearTimeout. Not: 0Gecikme olarak kullanıldığında bile , tüm tarayıcıların minimum zaman aşımı gecikme sınırı vardır (HTML5 spesifikasyonunda 4 ms olarak belirtilmiştir).
  • jQuery'nin $.postgeri araması , Ajax isteği başarıyla tamamlandığında (ve eğer) ileride çağrılabilir .
  • Node.js , gelecekte dosya başarıyla okunduğunda veya bir hata atıldığında fs.readFileçağrılabilir .

Her durumda, gelecekte çalışabilecek bir geri aramamız var . Bu "gelecekte bir zaman" bizim eşzamansız akış dediğimiz şeydir .

Eşzamansız yürütme, eşzamanlı akış dışına itilir. Yani, zaman uyumsuz kod , eşzamanlı kod yığını yürütülürken hiçbir zaman çalıştırılmayacaktır. JavaScript'in tek iş parçacıklı olmasının anlamı budur.

Daha spesifik olarak, JS motoru boştayken - bir (a) senkron kod yığını çalıştırmadığında - eşzamansız geri aramaları (örneğin süresi dolan zaman aşımı, alınan ağ yanıtı) tetikleyen olayları yoklayacak ve bunları birbiri ardına yürütecektir. Bu, Olay Döngüsü olarak kabul edilir .

Yani, elle çizilmiş kırmızı şekillerde vurgulanan eşzamansız kod, yalnızca kendi ilgili kod bloklarında kalan eşzamanlı kodun tamamı çalıştırıldıktan sonra çalıştırılabilir:

Kısacası, geri çağırma işlevleri eşzamanlı olarak oluşturulur ancak eşzamansız olarak yürütülür. Bir asenkron işlevin yürütüldüğünü bilene kadar yürütülmesine güvenemezsiniz ve bunu nasıl yaparsınız?

Gerçekten çok basit. Eşzamansız işlevin yürütülmesine bağlı olan mantık, bu eşzamansız işlevin içinden başlatılmalı / çağrılmalıdır. Örneğin, alerts ve console.logleri geri çağırma işlevinin içine taşımak beklenen sonucu verir, çünkü sonuç o noktada mevcuttur.

Kendi geri arama mantığınızı uygulama

Çoğu zaman, eşzamansız bir işlevin sonucuyla daha fazla şey yapmanız veya eşzamansız işlevin nerede çağrıldığına bağlı olarak sonuçla farklı şeyler yapmanız gerekir. Biraz daha karmaşık bir örneği ele alalım:

var outerScopeVar;
helloCatAsync();
alert(outerScopeVar);

function helloCatAsync() {
    setTimeout(function() {
        outerScopeVar = 'Nya';
    }, Math.random() * 2000);
}

Not: Ben kullanıyorum setTimeoutgenel asenkron fonksiyonu olarak rastgele bir gecikme ile, aynı örnek Ajax için geçerlidir readFile, onloadve diğer asenkron akışı.

Bu örnek açıkça diğer örneklerle aynı sorundan muzdariptir, asenkron işlevin yürütülmesini beklememektedir.

Kendimize ait bir geri arama sistemi uygulayarak bunun üstesinden gelelim. Öncelikle, outerScopeVarbu durumda tamamen yararsız olan o çirkinlikten kurtuluyoruz . Sonra bir fonksiyon argümanını kabul eden bir parametre ekliyoruz, geri aramamız. Eşzamansız işlem bittiğinde, bu geri aramayı sonucu geçirerek çağırıyoruz. Uygulama (lütfen yorumları sırayla okuyun):

// 1. Call helloCatAsync passing a callback function,
//    which will be called receiving the result from the async operation
helloCatAsync(function(result) {
    // 5. Received the result from the async function,
    //    now do whatever you want with it:
    alert(result);
});

// 2. The "callback" parameter is a reference to the function which
//    was passed as argument from the helloCatAsync call
function helloCatAsync(callback) {
    // 3. Start async operation:
    setTimeout(function() {
        // 4. Finished async operation,
        //    call the callback passing the result as argument
        callback('Nya');
    }, Math.random() * 2000);
}

Yukarıdaki örneğin kod pasajı:

// 1. Call helloCatAsync passing a callback function,
//    which will be called receiving the result from the async operation
console.log("1. function called...")
helloCatAsync(function(result) {
    // 5. Received the result from the async function,
    //    now do whatever you want with it:
    console.log("5. result is: ", result);
});

// 2. The "callback" parameter is a reference to the function which
//    was passed as argument from the helloCatAsync call
function helloCatAsync(callback) {
    console.log("2. callback here is the function passed as argument above...")
    // 3. Start async operation:
    setTimeout(function() {
    console.log("3. start async operation...")
    console.log("4. finished async operation, calling the callback, passing the result...")
        // 4. Finished async operation,
        //    call the callback passing the result as argument
        callback('Nya');
    }, Math.random() * 2000);
}

Çoğu zaman gerçek kullanım durumlarında, DOM API ve çoğu kitaplık, geri arama işlevini zaten sağlar ( helloCatAsyncbu örnek örnekteki uygulama). Yalnızca geri arama işlevini iletmeniz ve eşzamanlı akış dışında çalışacağını anlamanız ve buna uyum sağlamak için kodunuzu yeniden yapılandırmanız gerekir.

Eşzamansız doğası nedeniyle, eşzamansız returnbir akıştan geri aramanın tanımlandığı eşzamanlı akışa geri dönmenin imkansız olduğunu da fark edeceksiniz , çünkü eşzamansız geri aramalar, eşzamanlı kodun yürütülmesi bittikten uzun süre sonra yürütülür.

returnEşzamansız bir geri aramadan bir değer almak yerine , geri arama modelini veya ... Promises'i kullanmanız gerekecektir.

Sözler

Tutmak için yollar bulunmasına rağmen geri arama cehennem vanilya JS ile koyunda, vaat popülaritesi artmaktadır ve halen ES6 içinde standardize ediliyor (bkz - MDN Promise ).

Promises (aka Futures), eşzamansız kodun daha doğrusal ve dolayısıyla hoş bir şekilde okunmasını sağlar, ancak tüm işlevlerini açıklamak bu sorunun kapsamı dışındadır. Bunun yerine, bu mükemmel kaynakları ilgilenenlere bırakacağım:


JavaScript eşzamansızlığı hakkında daha fazla okuma materyali


Not: Bu yanıtı Topluluk Wiki olarak işaretledim, bu nedenle en az 100 itibara sahip herkes onu düzenleyebilir ve geliştirebilir! Lütfen bu yanıtı geliştirmekten çekinmeyin veya isterseniz tamamen yeni bir yanıt gönderin.

Ajax ile ilgisi olmayan eşzamansızlık sorunlarına yanıt vermek için bu soruyu kanonik bir konuya dönüştürmek istiyorum (bunun için bir AJAX çağrısından yanıt nasıl geri getirilir? ), Bu nedenle bu konunun olabildiğince iyi ve yararlı olması için yardımınıza ihtiyacı var !

158 Matt May 29 2014 at 16:09

Fabrício'nun yanıtı yerinde; ama onun cevabını, eşzamansızlık kavramını açıklamaya yardımcı olacak bir analojiye odaklanan daha az teknik bir şeyle tamamlamak istedim .


Bir Analoji ...

Dün, yaptığım iş bir meslektaşımdan bazı bilgiler gerektirdi. Onu aradım; konuşma şöyle geçti:

Beni : Merhaba Bob, nasıl bilmek gerekir foo 'd çubuğu ' geçen hafta d. Jim bununla ilgili bir rapor istiyor ve bunun ayrıntılarını bilen tek kişi sensin.

Bob : Elbette, ama 30 dakika kadar sürer mi?

Ben : Bu harika Bob. Bilgiye sahip olunca beni ara!

Bu noktada telefonu kapattım. Raporumu tamamlamak için Bob'dan bilgi almam gerektiğinden, raporu bıraktım ve onun yerine bir kahve içmeye gittim, sonra bir e-postayı yakaladım. 40 dakika sonra (Bob yavaş), Bob aradı ve bana ihtiyacım olan bilgiyi verdi. Bu noktada, ihtiyacım olan tüm bilgilere sahip olduğum için raporumla işime devam ettim.


Bunun yerine sohbetin böyle gittiğini hayal edin;

Beni : Merhaba Bob, nasıl bilmek gerekir foo 'd çubuğu ' geçen hafta d. Jim, bununla ilgili bir rapor ve bununla ilgili ayrıntıları bilen tek kişi sensin.

Bob : Elbette, ama 30 dakika kadar sürer mi?

Ben : Bu harika Bob. Bekleyeceğim.

Orada oturdum ve bekledim. Ve bekledi. Ve bekledi. 40 dakikadır. Beklemekten başka bir şey yapmamak. Sonunda Bob bana bilgi verdi, kapattık ve raporumu tamamladım. Ama 40 dakikalık üretkenliği kaybettim.


Bu eşzamansız ve eşzamanlı davranıştır

Sorumuzdaki tüm örneklerde olan tam olarak budur. Bir görüntüyü yüklemek, diskten bir dosya yüklemek ve AJAX aracılığıyla bir sayfa talep etmek, hepsi yavaş işlemlerdir (modern bilgi işlem bağlamında).

JavaScript, bu yavaş işlemlerin tamamlanmasını beklemek yerine, yavaş işlem tamamlandığında çalıştırılacak bir geri arama işlevi kaydetmenize izin verir. Ancak bu arada, JavaScript diğer kodları çalıştırmaya devam edecektir. JavaScript'in yavaş işlemin tamamlanmasını beklerken başka bir kod çalıştırması , davranışı eşzamansız hale getirir . JavaScript başka bir kodu çalıştırmadan önce işlemin tamamlanmasını bekleseydi, bu eşzamanlı bir davranış olurdu .

var outerScopeVar;    
var img = document.createElement('img');

// Here we register the callback function.
img.onload = function() {
    // Code within this function will be executed once the image has loaded.
    outerScopeVar = this.width;
};

// But, while the image is loading, JavaScript continues executing, and
// processes the following lines of JavaScript.
img.src = 'lolcat.png';
alert(outerScopeVar);

Yukarıdaki kodda lolcat.png, bir sloooow işlemi olan JavaScript'in yüklenmesini istiyoruz . Geri çağırma işlevi, bu yavaş işlem tamamlandıktan sonra çalıştırılacaktır, ancak bu arada JavaScript sonraki kod satırlarını işlemeye devam edecektir; yani alert(outerScopeVar).

Bu nedenle uyarıların gösterildiğini görüyoruz undefined; çünkü alert()hemen işlenir ziyade görüntü yüklendikten sonra.

Bizim kodunu çözmek için, yapmamız gereken tüm taşımaktır alert(outerScopeVar)kod içine geri arama işlevi. Bunun bir sonucu olarak, artık outerScopeVarglobal değişken olarak bildirilen değişkene ihtiyacımız yok .

var img = document.createElement('img');

img.onload = function() {
    var localScopeVar = this.width;
    alert(localScopeVar);
};

img.src = 'lolcat.png';

Her zaman bir geri aramanın işlev olarak belirtildiğini görürsünüz, çünkü JavaScript'te bazı kodu tanımlamanın tek * yolu budur, ancak daha sonraya kadar çalıştırmaz.

Bu nedenle, tüm örneklerimizde function() { /* Do something */ }geri çağırma; tüm örnekleri düzeltmek için tek yapmamız gereken işlemin yanıtına ihtiyaç duyan kodu oraya taşımak!

* Teknik eval()olarak da kullanabilirsiniz , ancak bu amaç için eval()kötüdür


Arayan kişiyi nasıl bekletebilirim?

Şu anda buna benzer bir kodunuz olabilir;

function getWidthOfImage(src) {
    var outerScopeVar;

    var img = document.createElement('img');
    img.onload = function() {
        outerScopeVar = this.width;
    };
    img.src = src;
    return outerScopeVar;
}

var width = getWidthOfImage('lolcat.png');
alert(width);

Ancak artık bunun return outerScopeVarhemen gerçekleştiğini biliyoruz ; onloadgeri çağırma işlevi değişkeni güncellemeden önce . Bu, getWidthOfImage()geri dönmeye undefinedve undefineduyarılmaya yol açar .

Bunu düzeltmek için, çağıran işlevin getWidthOfImage()bir geri aramayı kaydetmesine izin vermeli, ardından genişliğin uyarısını bu geri arama içinde olacak şekilde hareket ettirmeliyiz;

function getWidthOfImage(src, cb) {     
    var img = document.createElement('img');
    img.onload = function() {
        cb(this.width);
    };
    img.src = src;
}

getWidthOfImage('lolcat.png', function (width) {
    alert(width);
});

... daha önce olduğu gibi, global değişkenleri kaldırabildiğimize dikkat edin (bu durumda width).

75 JohnnyHK Jan 21 2015 at 06:42

İşte hızlı bir referans arayan insanlar için daha kısa bir cevap ve vaatler ve eşzamansız / bekleme kullanan bazı örnekler.

Eşzamansız bir yöntemi çağıran (bu durumda setTimeout) ve bir mesaj döndüren bir işlev için (işe yaramayan) saf yaklaşımla başlayın :

function getMessage() {
  var outerScopeVar;
  setTimeout(function() {
    outerScopeVar = 'Hello asynchronous world!';
  }, 0);
  return outerScopeVar;
}
console.log(getMessage());

undefinedbu durumda günlüğe kaydedilir çünkü getMessagegeri setTimeoutarama çağrılmadan ve güncellenmeden önce döner outerScopeVar.

Bunu çözmenin iki ana yolu geri aramaları ve vaatleri kullanmaktır :

Geri aramalar

Buradaki değişiklik , sonuçları mevcut olduğunda çağıran koda geri göndermek için çağrılacak getMessagebir callbackparametreyi kabul etmesidir .

function getMessage(callback) {
  setTimeout(function() {
    callback('Hello asynchronous world!');
  }, 0);
}
getMessage(function(message) {
  console.log(message);
});

Sözler

Sözler, geri aramalardan daha esnek bir alternatif sağlar çünkü bunlar, birden çok eşzamansız işlemi koordine etmek için doğal olarak birleştirilebilir. Bir Promises / A + standart uygulaması, node.js (0.12+) ve birçok mevcut tarayıcıda yerel olarak sağlanır, ancak Bluebird ve Q gibi kitaplıklarda da uygulanır .

function getMessage() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('Hello asynchronous world!');
    }, 0);
  });
}

getMessage().then(function(message) {
  console.log(message);  
});

jQuery Ertelenenler

jQuery, Ertelenenleri ile vaatlere benzer işlevsellik sağlar.

function getMessage() {
  var deferred = $.Deferred();
  setTimeout(function() {
    deferred.resolve('Hello asynchronous world!');
  }, 0);
  return deferred.promise();
}

getMessage().done(function(message) {
  console.log(message);  
});

eşzamansız / bekleyin

JavaScript ortamınız asyncve await(Node.js 7.6+ gibi) için destek içeriyorsa , vaatleri asyncişlevler içinde eşzamanlı olarak kullanabilirsiniz :

function getMessage () {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve('Hello asynchronous world!');
        }, 0);
    });
}

async function main() {
    let message = await getMessage();
    console.log(message);
}

main();
58 JohannesFahrenkrug Dec 08 2015 at 23:48

Bariz olanı ifade etmek için kupa temsil eder outerScopeVar.

Eşzamansız işlevler şöyle olabilir ...

14 Teja Feb 26 2016 at 10:59

Diğer cevaplar mükemmel ve ben sadece buna basit bir cevap vermek istiyorum. Sadece jQuery eşzamansız çağrılarla sınırlama

Tüm ajax çağrıları ( $.getveya $.postveya dahil $.ajax) asenkrondur.

Örneğinizi düşününce

var outerScopeVar;  //line 1
$.post('loldog', function(response) {  //line 2
    outerScopeVar = response;
});
alert(outerScopeVar);  //line 3

Kod yürütme 1. satırdan başlar, 2. satırdaki değişkeni ve tetikleyicileri ve eşzamansız çağrıyı bildirir (yani talep sonrası) ve son talebin yürütülmesini tamamlamasını beklemeden 3. satırdan yürütmeye devam eder.

Gönderi isteğinin tamamlanmasının 10 saniye sürdüğünü söyleyelim, değeri outerScopeVarancak bu 10 saniye sonra ayarlanacaktır.

Denemek için,

var outerScopeVar; //line 1
$.post('loldog', function(response) {  //line 2, takes 10 seconds to complete
    outerScopeVar = response;
});
alert("Lets wait for some time here! Waiting is fun");  //line 3
alert(outerScopeVar);  //line 4

Şimdi bunu çalıştırdığınızda, 3. satırda bir uyarı alırsınız. Şimdi, gönderi isteğinin bir değer döndürdüğünden emin olana kadar bir süre bekleyin. Ardından, uyarı kutusunda Tamam'a tıkladığınızda, bir sonraki uyarı, beklediğiniz için beklenen değeri yazdırır.

Gerçek hayat senaryosunda kod,

var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
    alert(outerScopeVar);
});

Eşzamansız çağrılara bağlı olan tüm kod, eşzamansız bloğun içine veya eşzamansız çağrıları bekleyerek taşınır.

11 TomSebastian Oct 27 2015 at 13:35

Tüm bu senaryolarda outerScopeVar, eşzamansız olarak değiştirilir veya bir değer atanır veya daha sonraki bir zamanda meydana gelir (bazı olayların meydana gelmesi için bekleme veya dinleme), mevcut yürütme beklemeyecektir. Yani tüm bu durumlarda, mevcut yürütme akışıouterScopeVar = undefined

Her bir örneği tartışalım (eşzamansız olarak adlandırılan veya bazı olayların meydana gelmesi için geciken kısmı işaretledim):

1.

Burada o belirli olay üzerine yürütülecek bir olay listesi kaydederiz. Burada görüntünün yüklenmesi.Sonra mevcut yürütme sonraki satırlarla devam eder img.src = 'lolcat.png';ve alert(outerScopeVar);bu arada olay gerçekleşmeyebilir. yani işlev img.onload, belirtilen görüntünün eşzamanlı olarak yüklenmesini bekler. Bu, aşağıdaki tüm örnekte gerçekleşecektir - olay farklı olabilir.

2.

Burada zaman aşımı olayı, belirtilen süreden sonra işleyiciyi çağıracak rolü oynar. İşte burada 0, ancak yine de eşzamansız bir olayı kaydeder Event Queue, bu, garantili gecikmeyi yapan için yürütmenin son konumuna eklenecektir .

3.

Bu sefer ajax geri araması.

4.

Düğüm, eşzamansız kodlamanın kralı olarak düşünülebilir.Burada işaretli işlev, belirtilen dosyayı okuduktan sonra yürütülecek bir geri arama işleyicisi olarak kaydedilir.

5.

Açık sözler (gelecekte bir şeyler yapılacak) eşzamansızdır. bkz JavaScript Ertelenmiş, Promise ve Geleceğe arasındaki farklar nelerdir?

https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript