关于javascript:JS如何返回异步调用的结果

2次阅读

共计 3557 个字符,预计需要花费 9 分钟才能阅读完成。

这个问题作者认为是所有从后端转向前端开发的程序员,都会遇到的第一问题。JS 前端编程与后端编程最大的不同,就是它的异步机制,同时这也是它的外围机制。

为了更好地阐明如何返回异步调用的后果,先看三个尝试异步调用的示例吧。

示例一:调用一个后端接口,返回接口返回的内容

function foo() {
  var result
  $.ajax({
    url: "...",
    success: function(response) {result = response}
  });
  return result // 返回:undefined
}

函数 foo 尝试调用一个接口并返回其内容,但每次执行都只会返回 undefiend。

示例二:应用 Promise 的 then 办法,同样是调用接口而后返回内容

function foo() {
  var result
  fetch(url).then(function(response) {result = response})
  return result // 返回:undefined
}

与上一个示例的调用一样,也只会返回 undefined。

示例三:读取本地文件,而后返回其内容

function foo() {
  var result
  fs.readFile("path/to/file", function(err, response) {result = response})
  return result // 返回:undefined
}

毫无意外这个示例的调用后果也是 undefined。

为什么?

因为这三个示例波及的三个操作————ajax、fetch、readFile 都是异步操作,从操作指令收回,到拿到后果,这两头有一个工夫距离。无论你的机器性能如许强劲,这个距离也无奈齐全抹掉。这是由 JS 的主线程是单线程而决定的,JS 代码执行到肯定地位的时候,它不能期待,期待意味着用户界面的卡顿,这是用户不能容忍的。JS 采纳异步线程优化该场景,当主线程中有异步操作发动时,主线程不会阻塞,会持续向下执行;当异步操作有数据返回时,异步线程会被动告诉主线程:“Hi,老大,数据来了,当初要用吗?”

“好的!马上给我。”

这样异步线程把异步代码推给主线程,异步代码才得以执行。对于下面三个示例而言,result = response 就是它们的异步代码。

上面作者画一张辅助了解这种机制吧:

当异步线程筹备好数据的时候,主线程也不是马上就能解决,只有当主线程有闲暇了,并且后面没有排队期待解决的数据了,新的异步数据能力得以解决。

在理解了 JS 的异步机制当前,上面看后面三个示例如何正确改写。

回调函数:最古老的异步后果返回形式

先看示例一,应用回调函数改写:

function foo(callback) {
  $.ajax({
    url: "...",
    success: function(response) {callback(response)
    }
  });
  // return result // 返回:undefined
}

在调用函数 foo 的时候,当时传递进来一个 callback,当 ajax 操作取到接口数据的时候,将数据传递给 callback,由 callback 自行处理。

这种基于回调的解决方案,尽管“奇妙”地解决了问题,但在存在多层异步回调的简单我的项目中,往往因为一个操作依赖于多个异步数据而造成“回调噩梦”。

ES2015:应用 Promise 对象与 then 办法链式调用

第二种改良的计划,不应用回调函数,而是应用 ES2015 中新增的 Promise 及其 then 办法,上面以示例二进行革新:

function foo() {return new Promise(function(resolve, reject) {fetch(url).then(function(response) {resolve(response)
    })
  })
}
foo().then(function(res){console.log(res)
})..catch(function(err) {//})

foo 返回一个 Promise 对象,留神,Promise 仅是一个可能承载正确数据的容器,它并不是数据。在应用它的,须要调用它的 then 办法能力获得数据(在有数据返回的时候)。与 then 同时存在的另一个有用的办法是 catch,它用于捕获异步操作可能呈现的异样,解决可能的谬误对增强鲁棒性至关重要,这个 catch 办法不容忽视。

留神:示例中的 fetch 办法作者没有给出具体实现,它在这里是作为一个返回 Promise 对象的异步操作被看待的,也因而咱们看到了,在这个办法被调用后返回的对象上,也能够紧跟着调用 then 办法(第 3 行)。

然而,这种应用 Promise 的解决方案就完满了吗,就没有问题了吗?显然不是的。

ES2017:应用 async/await 语法关键字

过多的“紧随”格调的 then 办法调用及 catch 办法调用,让代码的前后逻辑不清晰;当咱们浏览这样的代码时,并不是从上向下瀑布式浏览的,而是时而上、时而下跳动着浏览的,这很不难受。不仅浏览时不难受,编写时也很难以用一种像后端编程那样的从上向下的简洁的逻辑组织代码。

上面开始开始应用 ES2017 规范中提供 async/await 语法关键字,对示例三进行改写:

function foo() {return new Promise(function(resolve, reject) {fs.readFile("path/to/file", function(err, response) {resolve(response)
    })
  })
}
(async function(){const res = await foo().catch(console.log)
  console.log(res)
})()

基于 async/await 语法关键字的计划,是应用 Promise 的计划的升级版,在这个计划中也应用了 Promise。第 8 行~ 第 11 行,这是一个 IIFE(立刻调用函数表达式),之所以要用一个只应用一次的长期匿名函数将第 9 行~ 第 10 行的代码包裹起来,是因为 await 必须用在一个被 async 关键字润饰的函数或办法中,只能间接用到顶层的文件作用域或模块作用域下。

应用这种计划的优化是,代码能够像后端编程那样从上向下写,构造能够很清晰。这也是一种被称为“异步转同步”的 JS 编程范式,在前端开发中已被广泛承受。

留神,“异步转同步”并没有真正扭转异步代码,异步代码依然是异步代码,它们依然会在异步线程中先默默地执行,等有数据返回了再告诉主线程解决。当咱们应用这种编程模式的时候,肯定不要在主线程下来 await 一个 Promise,能够发动异步操作,让异步操作像葡萄一样挂在主线程上,但不能期待它们返回了再往下执行。

jQuery 的 Deferred Object(提早对象)

先看一段 Promise+then 办法格调的 jQuery 代码:

$.ajax({
  url: "test.html",
  context: document.body
}).done(function() {$(this).addClass("done")
});

第 4 行,这里的 done 办法是 jQuery 自行实现的,$.ajax 办法返回的是一个 DeferredObject(提早对象),这个对象上有 done 办法,这个办法与 Promise 的 then 相似。

jQuery 成名在前,在 ES2015 规范诞生之前,jQuery 的 DeferredObject 就曾经被定义了。Promise 自身并没有神奇的中央,它能够发挥作用,次要依赖的是在 JS 中,Object 是援用对象,继承于 Object 原型的 Promise 也是援用对象,当异步操作发动时,只有一个“空”的 Promise 被创立了,然而它的援用被放弃了;当数据回来的时候,数据再被“装填”进这个对象,这样通过先前持有的援用,异步代码便能够拜访到对象上携带的数据。

Promise 的胜利,更多是编程思维上的胜利,Promise 的胜利,也是编程思维上的胜利。 所有一种语言中编程思维上的胜利,在其余语言中都能够被学习和借鉴。 事实上在后端编程中,这种伪装成同步代码格调的异步编程思维也极其广泛,它们领有一个独特的名字,叫协程。

小结

在 JS 中解决异步调用的后果,最佳实际就是“异步转同步”:应用 Promise + async/await 语法关键字。在这里 async 总是与 await 成对呈现,一个 async 函数总是返回一个 Promise,一个 await 关键字总是在尝试“解开”一个 Promise,终局要么等到有价值的数据,要么异步呈现异步,什么也没有等到。为了防止出现异常,影响主线程的失常运行,个别要用 catch 躲避异样。


著作权归 LIYI 所有 基于 CC BY-SA 4.0 协定 原文链接:https://yishulun.com/posts/20…

正文完
 0