不使用回调函数的ajax请求实现(async和await简化回调函数嵌套)

在常规的服务器端程序设计中, 好比说爬虫程序, 发送http请求的过程会使整个执行过程阻塞,直到http请求响应完成代码才会继续执行, 以php为例子php

$url = "http://www.google.com.hk";
$result = file_get_contents($url);
echo $result;

当代码执行到第二行时,程序便陷入了等待,直到请求完成,程序才会继续往下跑将抓取到的html输出。这种作法的好处是代码简洁明了,运行流程清晰, 容易维护。 缺点就是程序的运行速度依赖于http请求的响应时间,影响程序的运行效率。 然而, 由于web程序自己特质的缘由,这种问题是避无可避的,程序依赖于http响应的结果和保证自身的迅速响应二者之间是存在矛盾的, 确定没法兼顾。html

可是在客户端程序或者非http应用的场景下是不存在相似的冲突的, 在Java或C#客户端编程中,碰到这种问题通常都是开启两个线程各干各的。而在JavaScript中,由于语言自己不支持多线程, 因此此类问题是使用回调函数来解决。前端

以最简单的前端ajax请求为例node

 $.get("data.json", function ( response ) {
        console.log("2");
 });
 console.log("1")

代码先输出1,再输出2,整个程序执行流程并未因http请求而被阻塞,回调函数方案完美的把问题解决。 然而,这只是最简单回调函数示例,假如回调函数嵌套了许多层呢?jquery

 

 

$.get("data1.json", function (response) {
    $.get(response.url, function (response) {
        $.get(response.url, function (response) {
            console.log(response);
        });
    });
});

回调嵌套的越深,代码运行逻辑就越难理清楚, 若是在上面代码的基础上再混入一些复杂的业务逻辑,那代码将会极难维护, 到时候遇到问题了剪不断理还乱的感受确定会让人红着眼睛骂娘。 虽然这种回调嵌套的场景在web前端开发中比较罕见, 但在nodejs服务器端开发领域仍是常见的。 那如何克服这个问题?假如用php来写, 那即是一件很轻松的事了。es6

$response = file_get_contents("data1.json");
$response1 =   file_get_contents($response["url"]);
$response2 =   file_get_contents($response1["url"]);
echo $response;

以php发送http请求的方案来实现, 代码逻辑就清晰了许多。 在古时候 ,JavaScript想以这种方式实现ajax那就是痴人说梦,可是当JavaScript升级至es6版本后,经过特定的途径也可实现这种写法。 在网上这种写法被称之为“以同步的方式编写异步代码”,可是我以为这种说法容易把人给搞迷糊,能够直接把这种写法称之为:“同步写法”, 由于里面的异步执行已经被隐藏了起来。 要实现这种写法必须使用async和await这两个关键字。在两个关键字是es7的范畴, es6还不支持,可是能够经过特定的工具将使用这两个关键字的代码转为es6的代码去执行, 好比说TypeScript和babel, 在此文中使用的代码示例都是由TypeScript实现。对于async和await的底层机制这里就不详述了, 以避免将文章的篇幅拖的很长,这里就讲解一下这两个关键字能实现的效果。 先把上面用JavaScript实现的多层嵌套回调用同步的方式来改写, 代码以下web

async function ajax(url) {
    return new Promise(function (resolve, reject) {
        let ajaxSetting = {
            url: url,
            success: function (response) {
                resolve(response);
            },
            error: function () {
                reject("请求失败");
            }
        }
        $.ajax(ajaxSetting);
 
    });
}
 
async function run() {
    let response1 = await ajax("data1.json");
    let response2 = await ajax(response1["url"]);
    let response3 = await ajax(response2["url"]);
    console.log(response3);
}
 
//不阻塞
run();

代码由ajax和run这两个函数组成, ajax是对jquery ajax的封装,使之能不使用回调函数就能得到ajax的响应结果。 当函数被声明为async类型时,若是这个函数要有返回值 ,而且返回值要在某个回调函数中得到,那么这个函数的返回结果就只能是一个 Promise对象,就像示例的ajax函数同样,返回值若是是其它类型那就达不到指望的效果。 Promise构造函数的参数是一个函数,resolve和reject分别是这个函数的两个参数,同时这两个参数自身也是函数类型,这两个参数有着重要的意义,在这里它们的做用就是将ajax的响应内容给返回出去,resolve表示返回正常情况下的值, reject表示返回异常状态下的值。按照传统的编码方式, 能够将reject看做是抛出了一个异常,像throw "请求失败", 这样,在函数调用的外部能够用try catch进行捕获。将值传出去为何要经过这两个参数呢?由于没辙啊, 试想一下,ajax的回调函数中使用return语句, 意义何在?所以也只能变向的经过Promise将返回值扔给外部的调用者。 因此,使用async和await的第一个要点就是ajax

当函数要得到异步结果时,能够函数声明为async类型, 函数的返回值设为Promise类型对象,而Promise中的resolve和reject是用来向async函数返回结果的, 功效如同普通函数的return语句。编程

async类型函数要怎么使用呢?有两种方法,一种是直接调用, 直接调用的话函数前面async关键字就被忽略了, 调用函数返回的结果就是一个Promise对象, Promise对像如何使用在这里不进行深究,大体就是像下面这样的写法json

ajax("data1.json").then(function( response ){
……
});

仍是以回调函数的形式出现,改进代码所带来的意义并无体现。

另外一种方法是在调用函数时加上await关键字,await的意义就在于接收async函数中的Promise对象中resolve和reject传递的值 ,并且除非resolve和reject这两个函数在回调函数中被调用到了, 不然代码就一直被阻塞在那里?换句话说, resolve和reject的调用是用来通知await等待结束,代码能够继续执行了。 这种写法不就是以前千方百计想实现的同步写法么?跟php的写法区别在于多了 await、async、Promise这三个概念, 可是在不考虑其中的内部运行原理的话, 代码的执行流程上已经和同步的写法没一丝区别了。有一点须要注意, 假如须要在函数中使用await调用,那么这个函数也必须被声明为async类型, 不然编译出错, 程序没法正常运行。 因此, 第二个要点就是

await就是用来等待Promise对象中resolve和reject这两个函数的执行的,而且将这两个函数传递的参数看成返回结果赋给变量,如同run函数中的代码示例那样。 别外, await必须被夹在两个async中间, 一个是await调用的函数,一个是await所在的函数。

至于Promise中的reject,就是用来抛异常的, 在外await调用以外可以使用try catch捕获,代码以下

async function run() {
    try {
        let response1 = await ajax("data1.json");
        let response2 = await ajax(response1["url"]);
        let response3 = await ajax(response2["url"]);
        console.log(response3);
    } catch (ex) {
        console.log(ex);
    }
}

此文只是纯粹的讲解 await和async能起什么样的做用?如何使用?至于深刻细节方面的知识, 有兴趣的同窗能够去阮一峰的博客里学习, 附上连接地址

http://www.ruanyifeng.com/blog/2015/05/async.html

相关文章
相关标签/搜索