搞懂JavaScript异步请求,一篇文章就够了

前言


javascript 语言执行环境是单线程,就是一次只能完成一个任务,如果同时有多个任务,必须排队执行。这种模式的好处是实现较简单,执行环境单纯,缺点是只要有一个任务耗时长,后面的任务必须排队,拖延整个程序的执行效率。

同步 & 异步


同步

  • 在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
  • 也就是一旦调用开始,就必须等待其返回结果,程序的执行顺序和任务排列顺序一致。

异步

  • 发出调用请求后,调用者不必等待其返回结果再次执行其他操作,只要发出请求后,即可继续执行后续操作。
  • 异步任务不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个任务可以执行了,该任务才会进入主线程。
  • 程序的执行顺序和任务排列顺序不一致,是异步的。

异步请求在江湖中的地位非常重要,异步给JS带来了更多的可能,我们是不是遇到过这样的业务场景:后一个请求依赖前一个请求的结果,也就是一个请求依赖另一个请求,如果依赖层数再多一点,很容易出现回调地狱的现象。本文将介绍几种异步请求的处理方式,拭目以待。


回调地狱


大家在学习 Jquery 的时候都应该接触过回调函数,在这,再次总结一下到底什么是回调函数?

知乎推荐回答:你去商场买衣服,刚好你的 size 没有了,于是你把电话留给了店员,过几天,那件衣服补货了,店员给你打电话,你去店里把它买下来。在这个例子里,你的电话号码就是回调函数,你把电话留下叫做登记回调函数,店里补货了这个事件触发了回调函数,店员给你打电话是调用回调函数,你去店里买下来是响应回调事件。

回调函数就是 任何一个 被以该回调函数为其参数的其他方法调用的方法。

//callback
//读./pakage.json的内容,写入./p.json文件,读取成功 2s后打印"ok"

const fs = require('fs')
fs.readFile('./pakage.json',(err,info) => {
    fs.writeFile('./p.json',info,(err) => {
        if(!err) {
            setTimeout(() => {
                console.log('ok')
            },2000)
        }
    })
})

以上代码用来实现异步操作,没有什么问题,但如果业务不只是这两个文件,那还要很多个回调函数的嵌套吗?这样的代码可维护性,可读性都变得很差,这就是回调地狱。


Promise对象


Promise对象,返回一个“异步承诺”,不管是请求成功还是出错都会执行,promise 对象带有 resolve 和 reject 方法,可以请求结果进行分开处理。promise对象有三种状态:

  • pending:初始状态
  • fulfilled:成功操作,为表述方便,用 resolved 代替。
  • rejected:失败操作

promise的一个便利是,针对多个异步请求的情况,可以使用 Promise.all 方法来确保各个请求的执行返回,避免了“回调地狱”。

pending 状态可以转换为 fulfilled 或者 rejected ,并且只能转换一次,如果 pending 转化为其中一种状态,就不能转换为另一种状态了,并且 fulfilled 和 rejected 状态只能由 pending 转化而来,两者之间不能互相转换。

<script src="https://cdn.bootcss.com/bluebird/3.5.1/bluebird.min.js"></script>//如果低版本浏览器不支持Promise,通过cdn这种方式
 <script type="text/javascript">
     function loadImg(src) {
         var promise = new Promise(function (resolve, reject) {
             var img = document.createElement('img')
             img.onload = function () {
                 resolve(img)
             }
             img.onerror = function () {
                 reject('图片加载失败')
             }
             img.src = src
         })
         return promise
     }
     var src = 'https://www.imooc.com/static/img/index/logo_new.png'
     var result = loadImg(src)
     result.then(function (img) {
         console.log(1, img.width)
         return img
     }, function () {
         console.log('error 1')
     }).then(function (img) {
         console.log(2, img.height)
     })
  </script>

promise 多个串联操作

promise 可以做更多事情,比如,有若干异步任务,需要先做任务1,成功后做任务2,任何任务失败则不再继续并进行错误处理,要实现这样的异步任务,如果不用 Promise对象,需要一层一层的嵌套回调。有了 Promise ,我们只需要简单的写

job1.then(job2).then(job3).catch(handleError);

then 方法可以被同一个 promise 调用多次,返回一个新的 promise 对象,因此可以通过链式调用 then 方法,避免了地狱回调的嵌套回调。

promise 常用方法

promise.all()

试想一个页面聊天系统,需要从两个不同的URL获取用户的个人信息和好友列表,这两个任务是可以并行执行的,用 Promise.all() 实现如下。

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
    console.log(results); // 获得一个Array: ['P1', 'P2']
});

promise.race()

有些时候,同时发起两个请求获取信息,只需要获取先返回的数据即可,这种情况下,用 Promise.race()实现。

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
    console.log(result); // 'P1'
});

可能 p1 执行较快,promise 的 then() 将获得结果 p1,p2 仍在继续执行,但执行结果将被丢弃。


总结:

  • Promise.all接受一个promise对象的数组,待全部完成之后,统一执行success;
  • Promise.race接受一个包含多个promise对象的数组,只要有一个完成,就执行success

将上面例子做下修改,加深对这两者的理解:

var src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
var result1 = loadImg(src1)
var src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
var result2 = loadImg(src2)
Promise.all([result1, result2]).then(function (datas) {
    console.log('all', datas[0])//<img src="https://www.imooc.com/static/img/index/logo_new.png">
    console.log('all', datas[1])//<img src="https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg">
})
Promise.race([result1, result2]).then(function (data) {
    console.log('race', data)//<img src="https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg">
})

Async/Await


虽然promise 很便利了,但在代码简洁方面,还有待优化,因此由了 async/await ,在多个请求情况下更是可以“链式”声明,可以大大节省代码量。

Async/Await 是什么?

async本意是异步,而 await 可以认为是 async wait 的缩写,所以可以理解为:async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

另外,语法规定,await 只能出现在 async 函数中。

async起什么作用?

async 函数是如何处理它的返回值的?我们写一个 demo 测试一下

async function testAsync() {
    return "hello async";
}

const result = testAsync();
console.log(result);//Promise { 'hello async' }

async 函数返回的是一个 promise 对象,如果在函数中 return ,async 会通过 Promise.resolve() 封装成 Promise 对象。但如果 不 return ,会出现什么情况,很显然,它会返回 :Promise.resolve(undefined)

Promise 的特点是:无等待,所以在没有 await 的情况下执行 async 函数,会立即执行,返回一个 promise 对象,绝不会阻塞后面的语句,这和普通的 promise 对象函数没啥区别,关键在于 await 做了什么?

await 在等待一个 async 函数完成,因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值,如果 await 等到了要等的东西,比如说是一个 promise 对象,await 会阻塞后面的代码,等待 promise 对象 resolve ,然后得到 resolve 的值,作为 await 表达式的运算结果。

var step1 = async function () {
    return new Promise(function (resolve, reject) {
        let st = setTimeout(() => {
            resolve('step1');
        }, 1000);
    });
}

var step2 = function () {
    return new Promise(function (resolve, reject) {
        let st = setTimeout(() => {
            resolve('step2')
        }, 1000);
    });
}

var step3 = function () {
    return new Promise(function (resolve, reject) {
        let st = setTimeout(() => {
            resolve('step3')
        }, 1000);
    })
}
async function test() {
    var data1 = await step1();
    console.log(data1);
    var data2 = await step2();
    console.log(data2);
    var data3 = await step3();
    console.log(data3)
}
test();

最后


以上就是对js中异步处理几种方式的简单理解,理解异步,有助于对代码的进一步理解。 本文仅作为笔者个人学习记录,能力有限,如有错误,请指正。不胜感激~~~

猜你喜欢

转载自blog.csdn.net/weixin_42653522/article/details/106314957