详解JavaScript异步编程之Promise

一、前言

JavaScript是⼀⻔典型的异步编程脚本语⾔,在编程过程中会⼤量的出现异步代码的编写,在JS的整个发展历程中,对异步编程的处理⽅式经历了很多个时代,其中最典型也是现今使⽤最⼴泛的时代,就是Promise对象处理异步编程的时代。

异步编程是相对同步编程来说的,开发项目时,开发者总是希望,程序的执行顺利能按照编程的顺序从上至下执行,这样符合人的思维易于理解,但是现实开发中,一般都事与愿违,相信每个开发者或多或少遇到过,程序执行到某些复杂的、耗时的任务时,往往要开启异步任务去执行,这样就不会阻塞当前任务,让当前任务继续执行,当异步任务执行完后,再将异步任务执行完的结果传给当前任务使用。所以异步编程主要为提高程序整体的执行效率。

那么,Promise对象到底是什么呢?

二、上古时期的异步编程模式

长期以来,异步操作一直是JavaScript语言的痛点。在该语言的早期版本中,异步操作仅支持定义回调函数以指示异步操作已完成。异步行为的执行是一个常见的问题,通常可以通过一个充满嵌套回调函数的代码片段来解决,该代码片段通常称为“回调地狱”。

在过去的编程中JavaScript的主要异步处理⽅式,是采⽤回调函数的⽅式来进⾏处理。在前端开发过程中使⽤最多的异步流程就是AJAX请求,当系统中要求某个⻚⾯的n个接⼝保证有序调⽤的情况下就会出现下⾯的情况。看如下代码:

//获取类型数据
$.ajax({
  url: '/***',
  success: function(res) {
    const xId = res.id;
    //获取该类型的数据集合,必须等待回调执⾏才能进⾏下⼀步
    $.ajax({
      url: '/***',
      data: {
        //使⽤上⼀个请求结果作为参数调⽤下⼀个接⼝
        xxId: xId,
      },
      success: function(res1) {
        const xxId = res1.id;
        //得到指定类型集合
        $.ajax({
          url:'/***',
          data:{
            //使⽤上⼀个请求结果作为参数调⽤下⼀个接⼝
            xxxId: xxId,
          },
          success:function(res2){
            //得到指定类型集合
            ...
          }
        })
      }
    })
  }
})

图片
看如上代码,这三个任务必须按先后顺序执⾏,并且每一个请求执行前都要先拿到上⼀个请求运⾏的结果,那么我们不得不将代码编写为以上案例代码。该写法主要是为了保证代码的严格顺序要求,这样就避免不了⼤量的逻辑在回调函数中不停的进⾏嵌套,这也是我们经常听说的“回调地狱”。这种情况在很多⼈的代码中都出现过,如果流程复杂化,在⽹络请求中继续夹杂其他的异步流程,那么这样的代码就会变得难以维护了。

再举一个我们在开发业务中常见的例子——省市区三级联动。

// 假如接口和参数如下
省级接口:http://localhost:8080/province
市级接口:http://localhost:8080/city(参数:provinceId)
区级接口:http://localhost:8080/area(参数:cityId)

先请求省级接口,获取某个省的编号,在使用省的编号,请求市级接口,获取某个市的编号,最后使用市的编号,请求区县级接口,获取区县的名称。在没有Promise之前,就会出现如下代码:

// 1.请求省级接口,获取省的编号
axios.get('http://localhost:3000/province').then(res => {
  for (let item of res.data) {
    if (item.provinceId === 'xxx') {
      // 2.使用省的编号,请求市级接口, 获取市的编号
      axios.get(`http://localhost:3000/city?provinceId=${item.provinceId}`).then(res => {
        for (let item of res.data) {
          if (item.cityId === 'xxx') {
            // 3.使用长沙市的编号,请求区级接口,获取岳麓区的名称
            axios.get(`http://localhost:3000/area?cityId=${item.cityId}`).then(res => {
              for (let item of res.data) {
                if (item.countyId == 'xxx') {
                  console.log(item.countyName )
                }
              }
            })
          }
        }
      })
    }
  }
})

在这里插入图片描述

其它的例子诸如Node中的原始fs模块,操作⽂件系统等场景就不再一一列举了。由此,之所以在ECMA提案中出现Promise解决⽅案,就是因为此类代码导致了JS在开发过程中遇到的实际问题:回调地狱。当然解决回调地狱还有其他⽅案,本篇文章核⼼介绍Promise流程控制对象,因为它是解决回调地狱⾮常好的⽅案。

三、回调函数

在介绍Promise前,我们先了解一个重要的概念——回调函数!JavaScript语⾔中,有⼀个特殊的函数叫做回调函数。回调函数的特点是把函数作为变量看待,由于JavaScript变量可以作为函数的形参并且函数可以通过声明变量的⽅式匿名创建,所以我们可以在定义函数时将⼀个函数的参数当作函数来执⾏,进⽽在调⽤时在参数的位置编写⼀个执⾏函数。

//把fn当作函数对象那么就可以在test函数中使⽤()执⾏他
function test(fn){
  fn()
}
//那么运⾏test的时候fn也会随着执⾏,所以test中传⼊的匿名函数就会运⾏
test(function(){
  ...
})

上⾯的代码结构,就是JavaScript中典型的回调函数结构。按照我们在事件循环中介绍的JavaScript函数运⾏机制,会发现其实回调函数本身是同步代码,这是⼀个需要重点理解的知识点。

通常在编写JavaScript代码时,使⽤的回调嵌套的形式⼤多是异步函数,所以⼀些开发者可能会下意识的认为,凡是回调形式的函数都是异步流程。其实并不是这样的,真正的解释是:JavaScript中的回调函数结构,默认是同步的结构,由于JavaScript单线程异步模型的规则,如果想要编写异步的代码,必须使⽤回调嵌套的形式才能实现,所以回调函数结构不⼀定是异步代码,但是异步代码⼀定是回调函数结构。

那么为什么异步流程都需要回调函数?

请看如下代码,想一想输出的顺序是什么?

function test(fn) {
  fn()
}
console.log(1)
test(function() {
  console.log(2)
})
console.log(3)

很显然,这段代码的输出顺序应该是1、2、3,因为它属于直接进⼊执⾏栈的程序,会按照正常程序解析的流程输出。

上面的代码我们变换成如下代码,它的输出的顺序又是什么呢?

function test(fn) {
  setTimeout(fn, 0)
}
console.log(1)
test(function() {
  console.log(2)
})
console.log(3)

这段代码会输出1、3、2,因为在调⽤test的时候settimeout将fn放到了异步任务队列挂起了,等待主程序执⾏完毕之后才会执⾏。

再思考⼀个问题,如果我们有⼀个变量a的值为1,想要1秒之后设置他的值为2,并且我们想要在之后得到a的新结果,这个逻辑中如果1秒之后设置a为2采⽤的是setTimeout,那么我们在同步结构⾥能否实现?

let a = 1;
setTimeout(() => {
  a = 2
}, 1000)
console.log(a)

上述代码块输出的结果⼀定是1,根据JavaScript单线程异步模型的知识,可以得知,当前的代码块中setTimeout的回调函数是⼀个宏任务,会在本次的同步代码执⾏完毕后执⾏,所以声明a=1和输出a的值这两⾏代码会优先执⾏,这时对a设置为2的事件还没有发⽣,所以输出的结果就⼀定为1。

那么,上述的问题怎么才能实现呢?接下来对代码做如下改造,我们试图使⽤阻塞的⽅式来获取异步代码的结果。看如下代码:

let a = 1;
//依然使⽤setTimeout设置1秒的延迟设置a的值
setTimeout(function(){
  a = 2
},1000)
let d = new Date().getTime()
let d1 = new Date().getTime()
//采⽤while循环配合时间差来阻塞同步代码2秒
while(d1-d<2000){
  d1 = new Date().getTime()
}
console.log(a)

上面的同步代码会在while循环中阻塞2秒,所以console.log(a)这⾏代码会在2秒之后才能获得执⾏资源,但是最终输出的结果仍然是1。这是为什么呢?这⾥仍然可以通过JavaScript的运⾏模型来进⾏理解,由于单线程异步模型的规则是严格的同步在前异步靠后顺序,本案例的同步代码虽然阻塞了2秒,已经超过了setTimeout的等待时间,但是setTimeout中的宏任务到时间后,仅仅会被从⼯作线程移动到任务队列中进⾏等待。在时间到达1秒时,while循环没有执⾏结束,所以函数执⾏栈会被继续占⽤,直到循环释放并输出a之后,任务队列中的宏任务才能执⾏,所以这⾥就算setTimeout时间到了,也必须等待同步代码执⾏完毕,那么输出a的时候a=2的事件仍然没有发⽣,所以我们采⽤默认的上下结构永远拿不到异步回调中的结果,这也是为什么异步流程都是回调函数的原因。

据此我们可以知道想要真正的在2秒后获取a的新结果的代码结构是这样的:

//只有在这个回调函数中才能获取到a改造之后的结果
let a = 1;
setTimeout(function() {
  a = 2;
}, 1000)
//注册一个新的宏任务,让它在上一个宏任务后执
setTimeout(function() {
  console.log(a)
}, 2000)

到这⾥也就⼤概明⽩了回调函数的意义以及使⽤场景了。接下来就详细介绍Promise是什么以及使⽤Promise如何解决异步控制问题,而且Promise它是⼀个及特殊的存在,Promise中既包含同步的回调函数,⼜包含异步的回调函数。

四、什么是Promise

从上⾯的案例介绍得知Promise的作⽤是解决“回调地狱”,它的解决⽅式是将回调嵌套拆成链式调⽤,这样便可以按照上下顺序来进⾏异步代码的流程控制。那么Promise是什么以及如何实现这个能⼒的呢?

MDN上是这样解释的:Promise是一个对象,它代表了一个异步操作的最终完成或者失败。本质上Promise是一个函数返回的对象,我们可以在它上面绑定回调函数,这样我们就不需要一开始把回调函数作为参数传入这个函数了。

1、构造 Promise
Promise对象是⼀个JavaScript对象,在⽀持ES6语法的运⾏环境中作为全局对象提供,初始化⽅式如下:

new Promise(function (resolve, reject) {
  // 要做的事情...
});

我们先对Promise做⼀个简单的介绍:Promise对象的主要⽤途是通过链式调⽤的结构,将原本回调嵌套的异步处理流程,转化成“对象.then().then()…”的链式结构,这样虽然仍离不开回调函数,但是将原本的回调嵌套结构,转化成了连续调⽤的结构,这样就可以在阅读上编程上下左右结构的异步执⾏流程了。

看如下代码:

setTimeout(function(){
//第⼀秒后执⾏的逻辑
console.log('第⼀秒之后发⽣的事情')
  setTimeout(function(){
  //第⼆秒后执⾏的逻辑
  console.log('第⼆秒之后发⽣的事情')
    setTimeout(function(){
      //第三秒后执⾏的逻辑
      console.log('第三秒之后发⽣的事情')
    },1000)
  },1000)
},1000)

上⾯的代码,分3秒每间隔1秒运⾏1个任务,这三个任务必须按时间顺序执⾏,并且每个下⼀秒触发前都要先拿到上⼀秒运⾏的结果。现在用 Promise 来实现同样的功能。

//使⽤Promise拆解的setTimeout流程控制
const p = new Promise(function(resolve){
  setTimeout(function(){
    resolve()
  },1000)
})
p.then(function(){
  //第⼀秒后执⾏的逻辑
  console.log('第⼀秒之后发⽣的事情')
  return new Promise(function(resolve){
    setTimeout(function(){
      resolve()
    },1000)
  })
}).then(function(){
  //第⼆秒后执⾏的逻辑
  console.log('第⼆秒之后发⽣的事情')
  return new Promise(function(resolve){
    setTimeout(function(){
      resolve()
    },1000)
  })
}).then(function(){
  //第三秒后执⾏的逻辑
  console.log('第三秒之后发⽣的事情')
})

通过如上代码我们发现使⽤了Promise后的代码,将原来的3个setTimeout的回调嵌套,拆解成了三次then包裹的回调函数,按照上下顺序进⾏编写。这样我们从视觉上就可以按照⼈类的从上到下从左到右的线性思维来阅读代码,这样很容易能查看这段代码的执⾏流程,代价是代码的编写量增加了接近1倍。

2、Promise 的构造函数
Pomise对象相当于⼀个未知状态的对象,它的定义就是声明⼀个等待未来结果的对象,在结果发⽣之前他⼀直是初始状态,在结果发⽣之后他会变成其中⼀种⽬标状态,Promise在英⽂中是绝对保证的意思,所以在编程中Promise对象是⼀个⾮常严谨的对象,⼀定会按照约定执⾏,除使⽤不当外,不会出现任务灵异问题。

那么Promise本身具备三种状态:

pending:初始状态,也叫就绪状态,这是在Promise对象定义初期的状态,这时Promise仅仅做了初始化并注册了他对象上所有的任务。

fulfilled:已完成,通常代表成功执⾏了某⼀个任务,当初始化函数中的resolve执⾏时,Promise的状态就变更为fulfilled,并且then函数注册的回调函数会开始执⾏,resolve中传递的参数会进⼊回调函数作为形参。

rejected:已拒绝,通常代表执⾏了⼀次失败任务,或者流程中断,当调⽤reject函数时,catch注册的回调函数就会触发,并且reject中传递的内容会变成回调函数的形参。

三种状态之间的关系:

Promise中约定,当对象创建之后同⼀个Promise对象只能从pending状态变更为fulfilled或rejected中的其中⼀种,并且状态⼀旦变更就不会再改变,此时Promise对象的流程执⾏完成并且finally函数执⾏。

经过了上⾯的代码我们可以分析⼀下Promise的运⾏流程和结构,⾸先从运⾏流程上我们发现了new Promise中的回调函数确实是在同步任务中执⾏的,其次是如果这个回调函数内部没有执⾏resolve或者reject,那么p对象的后⾯的回调函数内部都不会有输出,⽽运⾏resolve函数之后.then和.finally就会执⾏,运⾏了reject之后.catch和.finally就会执⾏。

根据上⾯的分析,结合下⾯的代码实例来加深一下对Promise规则的了解,分析该对象的运⾏结果。

//实例化⼀个Promise对象
const p = new Promise(function(resolve,reject){
 
})
//通过链式调⽤控制流程
p.then(function(){
  console.log('then执⾏')
}).catch(function(){
  console.log('catch执⾏')
}).finally(function(){
  console.log('finally执⾏')
})

上⾯的Promise对象结构,⼀个Promise对象包含两部分回调函数,第⼀部分是new Promise时候传⼊的对象,这段回调函数是同步的,⽽.then/.catch/.finally中的回调函数是异步的,这⾥要记好。实际上,在控制台内会发现这段程序并没有任何输出,我们继续往下看。

console.log('起步')
const p = new Promise(function(resolve,reject){
  console.log('调⽤resolve')
  resolve('执⾏了resolve')
})
p.then(function(res){
  console.log(res)
  console.log('then执⾏')
}).catch(function(){
  console.log('catch执⾏')
}).finally(function(){
  console.log('finally执⾏')
})
console.log('结束')

上面这段程序运⾏⼀下会发现输出顺序为: 起步->调⽤resolve->结束->执⾏了resolve->then执⾏->finally执⾏。

接着我们再看下面这段代码:

console.log('起步')
const p = new Promise(function(resolve,reject){
  console.log('调⽤reject')
  reject('执⾏了reject')
})
p.then(function(res){
  console.log(res)
  console.log('then执⾏')
}).catch(function(res){
  console.log(res)
  console.log('catch执⾏')
}).finally(function(){
  console.log('finally执⾏')
})
console.log('结束')

上面这段程序运⾏⼀下会发现输出顺序为: 起步->调⽤reject->结束->执⾏了reject->catch执⾏->finally执⾏。

欲知后文如何,且听下回分解!!!

五、总结

分析了Promise的对象结构和状态后,我们了解了Promise的异步回调部分如何执⾏,取决于我们在初始化函数中的操作,并且初始化函数中⼀旦调⽤了resolve后⾯再执⾏reject也不会影响then执⾏,catch也不会执⾏,反之同理。⽽在初始化回调函数中,如果不执⾏任何操作,那么promise的状态就仍然是pending,所有注册的回调函数都不会执⾏。

写在最后:

一行代码,可能会创造出下一个让人惊叹的产品;一个创新,可能会开启一个全新的科技时代;一份初心,可能会影响到无数人的生活;无论是在大公司工作,还是在小团队奋斗;无论是资深的程序员,还是刚刚入行的新手;每个人的代码,都有力量改变世界。

创作不易,喜欢的老铁们加个关注,点个赞,后面会不定期更新干货和技术相关的资讯,速速收藏,谢谢!你们的一个小小举动就是对小编的认可,更是创作的动力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/345573.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

当软件开发具备了低代码的开发能力,难以想象会有多“香”

一、前言 低代码开发平台&#xff0c;一个号称能在几分钟的时间里开发出一套公司内部都可使用的应用系统开发工具。 很多人或许都隐隐听说过低代码&#xff0c;因为低代码不仅远名国外&#xff0c;国内的腾讯、阿里、华为、网易、百度等科技巨头也纷纷入局。 那么市面上都有哪些…

全桥RLC模态图具体分析

T0时刻&#xff0c;Q6,Q7,Q1.Q4开通&#xff0c;驱动为高电平&#xff0c;励磁电流线性上升,但是lm电流在to是为负电流&#xff0c;这时刻有给副边提供能量&#xff0c;Ip电流开始上升&#xff0c;这个时候给副边的电流也是从0开始上升,这个能量由励磁电感提供&#xff0c;Co给…

HCIA——27E-mall、MIME;POP3、IMAP的选择,解答

学习目标&#xff1a; 计算机网络 1.掌握计算机网络的基本概念、基本原理和基本方法。 2.掌握计算机网络的体系结构和典型网络协议&#xff0c;了解典型网络设备的组成和特点&#xff0c;理解典型网络设备的工作原理。 3.能够运用计算机网络的基本概念、基本原理和基本方法进行…

整理了一下常用的LaTeX数学公式语法,未完待续

为了方便对应&#xff0c;后面会拆一下 公式代码放入LaTeX编译环境中时&#xff0c;两边需要加入$$: $$公式代码$$ 1&#xff0c;分解示例 L^{A}T_{E}X\,2_{\epsilon} c^{2}a^{2}b^{2} \tau\phi \cos2\pi1 f\, \,a^{x}\,\,b \heartsuit \cos^{2}\theta \sin^{2}\theta 1.0…

Nodejs前端学习Day1

妈的&#xff0c;学vue3需要15.0以上的nodejs 文章目录 前言一、学习目标二、学习目录三、为什么JavaScript可以在浏览器中被执行四、为什么JavaScript可以操作DOM和BOM五、浏览器中的JavaScript运行环境总结 前言 妈的&#xff0c;学vue3需要15.0以上的nodejs 一、学习目标 二…

CNN经典网络模型(五):ResNet简介及代码实现(PyTorch超详细注释版)

目录 一、开发背景 二、网络结构 三、模型特点 四、代码实现 1. model.py 2. train.py 3. predict.py 4. spilit_data.py 五、参考内容 一、开发背景 残差神经网络(ResNet)是由微软研究院的何恺明、张祥雨、任少卿、孙剑等人提出的&#xff0c; 斩获2015年ImageNet竞赛…

SE通道注意力机制模块

简介 论文原址&#xff1a;https://arxiv.org/pdf/1709.01507.pdf 在深度学习领域&#xff0c;提升模型的表征能力一直是一个关键的研究方向。SE&#xff08;Squeeze-and-Excitation&#xff09;模块是一种引入通道注意力机制的方法&#xff0c;旨在让神经网络更加关注对当前…

我每天如何使用 ChatGPT

我们都清楚互联网的运作方式——充斥着各种“爆款观点”&#xff0c;极端分裂的意见&#xff0c;恶搞和无知现象屡见不鲜。 最近&#xff0c;大家对于人工智能&#xff08;AI&#xff09;特别是大语言模型&#xff08;LLMs&#xff09;和生成式 AI&#xff08;GenAI&#xff0…

【趣味游戏-08】20240123点兵点将点到谁就是谁(列表倒置reverse)

背景需求&#xff1a; 上个月&#xff0c;看到大4班一个孩子在玩“点兵点将点到谁就是谁”的小游戏&#xff0c;他在桌上摆放两排奥特曼卡片&#xff0c;然后点着数“点兵点将点到谁就是谁”&#xff0c;第10次点击的卡片&#xff0c;拿起来与同伴的卡片进行交换。他是从第一排…

Unity-Arduino Bluetooth Plugin蓝牙插件使用时需要注意的一些事项(附插件下载链接)

一些参考链接 1.Android 无法扫描蓝牙设备踩坑 2.权限相关 1-首先要明确你的蓝牙设备是经典蓝牙还是低功耗&#xff08;BLE)蓝牙&#xff1a; 转载&#xff1a;Android蓝牙开发—经典蓝牙和BLE&#xff08;低功耗&#xff09;蓝牙的区别 2.如果是BLE蓝牙&#xff0c;需要打勾…

what is `ContentCachingRequestWrapper` does?

ContentCachingRequestWrapper 是 Spring Framework 中提供的一种包装类&#xff0c;它扩展了 HttpServletRequestWrapper 类&#xff0c;用于缓存请求体的内容。 通常在处理 HTTP 请求时&#xff0c;原生的 HttpServletRequest 对象中的输入流 (getInputStream()) 只能被读取一…

SpringBoot-多数据源切换和事物处理(免费)

作者原始文章: SpringBoot-多数据源切换和事物处理 最新内容和改动请看上面的文章 安装 <dependency><groupId>com.gitee.huanminabc</groupId><artifactId>dynamic-datasource</artifactId><version>1.0.3-RELEASE</version> <…

【经验分享】豆瓣小组的文章/帖子怎么删除?

#豆瓣小组的文章/帖子怎么删除&#xff1f;# 第一步&#xff1a; 手机登录豆瓣app ↓ 点右下角“我” ↓ 然后在页面点击我的小组 ↓ 点我发布的 ↓ ↓ 再任意点开一个帖子 ↓ 在文章和帖子的右上角有一个笔状的图标&#xff0c;切记不是右上角的横三点… ↓ ↓ 最后点下边的…

git 对象压缩及垃圾对象清理

git 对象压缩及垃圾对象清理 这篇文章让我们来看看 git 的对象压缩机制&#xff0c;前面的几篇文章我们提到&#xff0c;在执行 git add 命令会会把文件先通过 zlib 压缩后放入到「暂存区」&#xff0c;我们先看看这个步骤&#xff1a; 我们这个实例中有一个 1.28m 的 index.…

6.php开发-个人博客项目Tp框架路由访问安全写法历史漏洞

目录 知识点 php框架——TP URL访问 Index.php-放在控制器目录下 ​编辑 Test.php--要继承一下 带参数的—————— 加入数据库代码 --不过滤 --自己写过滤 --手册&#xff08;官方&#xff09;的过滤 用TP框架找漏洞&#xff1a; 如何判断网站是thinkphp&#x…

​比特币大跌的 2 个原因

撰文&#xff1a;秦晋 原文来自Techub News&#xff1a;​比特币大跌的 2 个原因 比特币迎来大跌&#xff01;1 月 23 日凌晨&#xff0c;比特币跌破 40000 美元&#xff0c;为去年 12 月 4 日以来首次&#xff0c;日内跌超 3%。这是自 1 月 10 日美国证监会审批通过 11 只比…

Conda python运行的包和环境管理 入门

Conda系列&#xff1a; 翻译: Anaconda 与 miniconda的区别Miniconda介绍以及安装 Conda 是一个功能强大的命令行工具&#xff0c;用于在 Windows、macOS 和 Linux 上运行的包和环境管理。 本 conda 入门指南介绍了启动和使用 conda 创建环境和安装包的基础知识。 1. 准备…

学习推荐!!HTML5+CSS3从入门到精通

获取方式&#xff1a; 《HTML 5 CSS3从入门到精通》 《HTML5CSS3从入门到精通目录》 第1章 Web开发新时代 第2章 从HTML、XHTML到HTML5 第3章 创建HTML5文档 第4章 实战HTML5表单 第5章 实战HTML5画布 第6章 HTML5音频与视频 第7章 Web存储 第8章 离线应用 第9章 Workers多线…

K8S的HPA

horiztal Pod Autoscaling&#xff1a;pod的水平自动伸缩&#xff0c;这是k8s自带的模块&#xff0c;它是根据Pod占用cpu比率到达一定的阀值&#xff0c;会触发伸缩机制 Replication controller 副本控制器&#xff1a;控制pod的副本数 Deployment controller 节点控制器&…

从开发、部署到维护:SAAS与源代码小程序的全流程对比

在数字化时代&#xff0c;小程序已成为企业开展业务的重要工具。然而&#xff0c;小程序开发过程中存在多种形式&#xff0c;其中SAAS版本小程序和源代码小程序是最常见的两种。乔拓云SaaS系统作为业界领先的SaaS服务平台&#xff0c;为企业提供高效、便捷的小程序解决方案。与…
最新文章