• JavaScript 中的异步编程


    深入理解异步编程的核心 Promise

    一、Promise 的基本情况

    简单来说它就是一个容器,里面保存着某个未来才会结束的事件(通常是异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作对的消息。

    • Promise 对象在被创建出来时是待定的状态,它让你能够把异步操作返回最终的成功值或者失败原因,和对应的处理程序关联起来。

    1.待定(pending):初始状态,既没有被完成,也没有被拒绝
    2.已完成(fulfilled):操作成功完成
    3.已拒绝(rejected):操作失败

    function read(url) {return new Promise((resolve, reject) => {fs.readFile(url, 'utf-8', (err, data) => {if (err) reject(err)resolve(data)})})
    }
    read(A).then((data) => {return read(B)}).then((data) => {return read(C)}).then((data) => {return read(D)}).catch((reason) => {console.log(reason)}) 
    
    • 1
    • 2
    • 3

    待定状态的 Promise 对象执行的话,最后要么会通过一个值完成,要么会通过一个原因被拒绝。当其中一种情况发生时,我们用 Promise 的 then 方法排列起来的相关处理程序就会被调用。因为最后 Promise.prototype.then 和 Promise.prototype.catch 方法返回的是一个 Promise,所以它们可以继续被链式调用。

    • 从上图可以看出,我们最开始创建一个新的 Promise 返回给 P1,然后开始执行,状态是 pending,当执行 resolve 之后状态就切换为 fulfiller,执行 reject 之后就变成 rejected 的状态。

    二、Promise 如何解决回调地狱

    1.多层嵌套的问题
    2.每种任务的处理结果存在两种可能性(成功或失败),那么需要在每种任务执行结束后分别处理这两种可能性

    • Promise 的诞生,就是为了解决这两个问题。Promise 利用了三大技术手段来解决回调地狱:回调函数延迟绑定、返回值穿透、错误冒泡。
    let readFilePromise = (filename) => {return new Promise((resolve, reject) => {fs.readFile(filename, (err, data) => {if (err) {reject(err)} else {resolve(data)}})})
    }
    readFilePromise('1.json').then((data) => {return readFilePromise('2.json')
    })
    // 从上面的代码可以看出,回调函数不是直接声明的,而是通过后面的 then 方法传入的,即延迟传入,这就是回调函数延迟绑定。
    let x = readFilePromise('1.json').then((data) => {return readFilePromise('2.json') // 这是返回的 Promise
    })
    x.then(/* 内部逻辑省略 */)
    /* 我们根据 then 中回调函数的传入值创建不同类型的 Promise,然后把返回的 Promise 穿透到外层,以供后续的调用。
    这里的 X 指的就是内部返回的 Promise,然后在 X 后面可以依次完成链式调用。
    这便是返回值穿透的效果,这两种技术一起作用便可以将深层的嵌套回调写成下面的形式 */
    readFilePromise('1.json').then((data) => {return readFilePromise('2.json')}).then((data) => {return readFilePromise('3.json')}).then((data) => {return readFilePromise('4.json')})
    // 这样就显得清爽了许多,更重要的是,它更符合人的线性思维模式,开发体验也更好,这两种技术结合产生了链式调用的效果
    /* Promise 采用了错误冒泡的方法,每次任务执行结束后分别处理成功和失败的情况 */
    readFilePromise('1.json').then((data) => {return readFilePromise('2.json')}).then((data) => {return readFilePromise('3.json')}).then((data) => {return readFilePromise('4.json')}).catch((err) => {// XXX})
    /* 这样,前面产生的错误会一直向后传递,被 catch 接收到,就不用频繁地检查错误了。
    从上面的代码可以看出,Promise 解决效果也比较明显:实现链式调用,解决多层嵌套问题;
    实现错误冒泡一站式处理,解决多次任务中判断错误、增加代码混乱度的问题 */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    三、Promise 的静态方法

    1.all 方法

    • 语法:Promise.all(iterable)
    • 参数:一个可迭代对象,如 Array
    • 描述:此方法对于汇总多个 Promise 的结果很有用,在 ES6 中可以将多个 Promise.all 异步请求并行操作,返回结果一般有下面两种情况:1.当所有结果成功返回时按照请求顺序返回成功2.当其中有一个失败方法时,则进入失败方法
    // 1.获取轮播数据列表
    function getBannerList() {return new Promise((resolve, reject) => {setTimeout(() => {resolve('轮播数据')}, 300)})
    }
    // 2.获取店铺列表
    function getStoreList() {return new Promise((resolve, reject) => {setTimeout(function () {resolve('店铺数据')}, 500)})
    }
    // 3.获取分类列表
    function getCategoryList() {return new Promise((resolve, reject) => {setTimeout(function () {resolve('分类数据')}, 700)})
    }
    /* 从上面代码中可以看出,在一个页面中需要加载获取轮播图列表、获取店铺列表、获取分类列表这三个操作,页面需要同时发出请求进行页面渲染,这样用 Promise.all 来实现,看起来更清晰、一目了然 */
    function initLoad() {Promise.all([getBannerList(), getStoreList(), getCategoryList()]).then((res) => {console.log(res)}).catch((err) => {console.log(err)})
    }
    initLoad() 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.allSettled 方法

    Promise.allSettled 的语法及参数跟 Promise.all 类似,其参数接受一个 Promise 的数组,返回一个新的 Promise。唯一的不同在于,执行完之后不会失败,也就是说当 Promise.allSettled 全部处理完成后,我们可以拿到每个 Promise 的状态,而不管其是否处理成功。

    const resolved = Promise.resolve(2)
    const rejected = Promise.reject(-1)
    const allSettledPromise = Promise.allSettled([resolved, rejected])
    allSettledPromise.then(function (results) {console.log(results)
    })
    // 返回结果
    /* [{status:'fulfilled',value:2},{status:'rejected',reason:-1}
    ] */
    /* Promise.allSettled 最后返回的是一个数组,记录传进来的参数中每个 Promise 的返回值,
    这就是和 all 方法不太一样的地方。你也可以根据 all 方法提供的业务场景的代码进行改造,
    其实也能知道多个请求发出去之后,Promise 最后返回的每个参数的最终状态
     */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.any 方法

    • 语法:Promise.any(iterable)
    • 参数:iterable 可迭代的对象,例如 Array
    • 描述:any 方法返回一个 Promise,只要参数 Promise 实例有一个变成 fulfilled 状态,最后 any 返回的实例就会变成 fulfilled 状态;如果所有参数 Promise 实例都变成 rejected 状态,包装实例就会变成 rejected 状态
    const resolved = Promise.resolve(2)
    const rejected = Promise.reject(-1)
    const anyPromise = Promise.any([resolved, rejected])
    anyPromise.then(function (resolve) {console.log(results)
    })
    // 返回结果
    // 2
    /* 只要其中一个 Promise 变成 fulfilled 状态,那么 any 最后就返回这个 Promise。
    由于上面 resolved 这个 Promise 已经是 resolve 的了,故最后返回结果为 2 */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4.race 方法

    • 语法:Promise.race(iterable)
    • 参数:iterable 可迭代的对象,例如 Array
    • 描述:race 方法返回一个 Promise,只要参数的 Promise 之中有一个实例率先改变状态,则 race 方法的返回状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 race 方法的回调函数。
    /* 对于图片的加载,特别适合用 race 方法来解决,
    将图片请求和超时判断放在一起,用 race 来实现图片的超时判断。 */
    
    // 请求某个图片资源
    function requestImg() {var p = new Promise(function (resolve, reject) {var img = new Image()img.onload = function () {resolve(img)}img.src = 'http://www.baidu.com/img/flexible/logo/pc/result.png'})return p
    }
    // 延时函数,用于给请求计时
    function timeout() {var p = new Promise(function (resolve, reject) {setTimeout(() => {reject('图片请求超时')}, 5000)})return p
    }
    Promise.race([requestImg(), timeout()]).then(function (results) {console.log(results)}).catch(function (reason) {console.log(reason)})
    /* 采用 Promise 的方式来判断图片是否加载成功,
    也是针对 Promise.race 方法的一个比较好的业务场景 */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    四、总结

    最后

    最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



    有需要的小伙伴,可以点击下方卡片领取,无偿分享

  • 相关阅读:
    在MATLAB环境下访问外部函数的共享库文件
    什么是Spring容器中的组件
    【数据结构Java版】 初识泛型和包装类
    涛思 TDengine 2.6+版本 安装试用
    AI视频模型已成为科技领域的新热点
    Document Object Model
    JAVA基础(十一)
    图解LeetCode——1408. 数组中的字符串匹配(难度:简单)
    gcc工具链——动态库和静态库,makefile,cmake,环境变量
    Tomcat 调优之从 Linux 内核源码层面看 Tcp backlog
  • 原文地址:https://blog.csdn.net/web2022050903/article/details/127436506