为什么变量在函数内部修改后没有改变?【Js异步代码】

更新日期: 2021-09-08阅读: 958标签: 异步

鉴于以下示例,为什么outerScopeVar在所有情况下都是undefined?

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

为什么它会undefined在所有这些示例中输出?我不想要解决方法,我想知道为什么会这样。


答案

让我们首先追踪常见的行为。在所有示例中,outerScopeVar都在函数内部被修改。该函数显然不会立即执行,而是被分配或作为参数传递。这就是我们所说的回调

现在的问题是,该回调何时调用?视情况而定。让我们再次尝试追踪一些常见的行为:

  • img.onload可能会在将来的某个时间调用,当(以及如果)图像已成功加载时。
  • setTimeout可能会在将来的某个时间调用,在延迟到期并且超时没有被 取消之后clearTimeout。注意:即使使用0作为延迟,所有浏览器都有最小超时延迟上限(在 HTML5 规范中指定为 4ms)。
  • jQuery$.post的回调可能会在未来某个时间被调用,当(以及如果)Ajax 请求成功完成时。
  • 当文件被成功读取或抛出错误时,Node.jsfs.readFile可能会在未来某个时候被调用。

在所有情况下,我们都有一个可能在未来某个时候运行的回调。这个“将来的某个时候”就是我们所说的异步流

异步执行被推出同步流程。也就是说,异步代码永远不会在同步代码堆栈执行时执行。这就是 JavaScript 是单线程的含义。

更具体地说,当 JS 引擎空闲时——不执行一堆(a)同步代码——它将轮询可能触发异步回调的事件(例如超时超时、收到网络响应)并一个接一个地执行它们。这被视为事件循环。

简而言之,回调函数是同步创建但异步执行的。在您知道异步函数已执行之前,您不能依赖它的执行,以及如何做到这一点?

这很简单,真的。依赖于异步函数执行的逻辑应该从这个异步函数内部开始/调用。例如,在回调函数内移动alerts 和console.logs 将输出预期结果,因为该结果在该点可用。

实现自己的回调逻辑

通常,您需要对异步函数的结果执行更多操作,或者根据调用异步函数的位置对结果执行不同的操作。让我们处理一个更复杂的例子:

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

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

注意:我使用setTimeout随机延迟作为通用异步函数,同样的示例适用于 Ajax readFile、onload和任何其他异步流。

此示例显然与其他示例存在相同的问题,它不会等到异步函数执行。

让我们来解决它实现我们自己的回调系统。首先,我们摆脱了outerScopeVar在这种情况下完全没用的丑陋。然后我们添加一个接受函数参数的参数,我们的回调。当异步操作完成时,我们调用此回调传递结果。实现(请按顺序阅读评论):

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

上面例子的代码片段:

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

大多数情况下,在实际用例中,DOM API 和大多数库已经提供了回调功能(helloCatAsync本演示示例中的实现)。您只需要传递回调函数并了解它将在同步流之外执行,并重构您的代码以适应它。

您还会注意到,由于异步性质,不可能将return异步流中的值返回到定义回调的同步流,因为异步回调在同步代码已经完成执行后很长时间才执行。

不是return从异步回调中获取值,您将不得不使用回调模式,或者使用promise。


使用 promise 和 async/await

对于正在寻找快速参考以及一些使用 promise 和 async/await 的示例的人,这里有一个更简洁的答案。

从调用异步方法(在本例中setTimeout)并返回消息的函数的天真方法(不起作用)开始:

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

undefined在这种情况下被记录,因为getMessage在setTimeout调用回调和更新之前返回outerScopeVar。

解决它的两种主要方法是使用回调承诺

回调

这里的变化是getMessage接受一个callback参数,一旦可用,该参数将被调用以将结果传递回调用代码。

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

Promise

Promise 提供了一种比回调更灵活的替代方案,因为它们可以自然地组合以协调多个异步操作。

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

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

jQuery延迟

jQuery 提供的功能类似于带有 Deferred 的 promise。

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

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

异步/等待

如果您的 JavaScript 环境支持async和await(如 Node.js 7.6+),那么您可以在async函数内同步使用 promise :

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();

链接: https://www.fly63.com/article/detial/10700

通过alert方法,去理解js中阻塞、局部作用域、同步/异步任务

javascript中alert是Bom中的成员函数,alert对话框是模态的,具有阻塞性质的,不点击是不会执行后续代码的。js的阻塞是指在调用结果返回之前,当前线程会被挂起, 只有在得到结果之后才会继续执行。

如何优化async代码?更好的编写async异步函数

如何优化async代码?更好的编写async函数:使用return Promise.reject()在async函数中抛出异常,让相互之间没有依赖关系的异步函数同时执行,不要在循环的回调中/for、while循环中使用await,用map来代替它

【JS】异步处理机制的几种方式

Javascript语言的执行环境是单线程,异步模式非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。

js异步加载方式有哪些?_详解异步加载js的多种方案

js异步加载又被称为非阻塞加载,浏览器在下载JS的同时,还会进行后续页面处理。那么如何实现js异步加载呢?下面整理了多种实现方案供大家参考。异步加载js方案:Script Dom Element、onload时的异步加载、$(document).ready()、async属性、defer属性、es6模块type=module属性

Nodejs 处理异步(获取异步数据并处理)的方法

回调函数方式:将异步方法如readFile封装到一个自定义函数中,通过将异步方法得到的结果传给自定义方法的回调函数参数。事件驱动方式:使用node events模块,利用其EventEmitter对象

JS常用的几种异步流程控制

JavaScript引擎是基于单线程 (Single-threaded) 事件循环的概念构建的,同一时刻只允许一个代码块在执行,所以需要跟踪即将运行的代码,那些代码被放在一个任务队列 (job queue) 中

前端异步编程之Promise和async的用法

传统的异步解决方案采用回调函数和事件监听的方式,而这里主要记录两种异步编程的新方案:ES6的新语法Promise;ES2017引入的async函数;Generator函数(略)

异步的JavaScript

JS本身是一门单线程的语言,所以在执行一些需要等待的任务(eg.等待服务器响应,等待用户输入等)时就会阻塞其他代码。如果在浏览器中JS线程阻塞了,浏览器可能会失去响应,从而造成不好的用户体验。

js 多个异步的并发控制

请实现如下的函数,可以批量请求数据,所有的URL地址在urls参数中,同时可以通过max参数 控制请求的并发度。当所有的请求结束后,需要执行callback回调。发请求的函数可以直接使用fetch。

解读react的setSate的异步问题

将setState()认为是一次请求而不是一次立即执行更新组件的命令。为了更为可观的性能,React可能会推迟它,稍后会一次性更新这些组件。React不会保证在setState之后,能够立刻拿到改变的结果。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!