当前位置: 首页 > news >正文

从异步到promise

一,背景

1.1,js的单线程

这一切,要从js诞生之初说起,因为js是单线程的语言。

js单线程原因:作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?当然,这可以加锁来解决,但是加锁会变得更复杂,得不偿失。

1.2,同步异步的讨论

先简单理解下同步和异步:

同步操作可以理解为不花费时间的操作,而异步操作是需要一定时间的。

单线程意味着所有的任务都只能乖乖排队执行,就和我们排队过地铁闸机一样。但是有的人刷卡,有的人刷码,有的人刷脸,所花费的时间不一样,假如一个人手机没网了,就会卡住队伍,造成堵塞。这就是单线程带来的同步阻塞问题

为了解决这个问题,js引入了回调函数机制,对于一个IO操作,比如一个ajax,当发出一个异步请求后,程序不会阻塞在那里等待结果的返回,而是继续执行下面的代码。当请求成功获取到结果后,就会调用回调函数来处理后面的事情,这个就是异步非阻塞。在js中,异步操作的处理都是异步非阻塞。

所以可以说js是单线程异步编程语言。

如下代码:

console.log(1)
setTimeout(()=>{console.log(2)
},100)
console.log(3)
//执行结果:132

setTimeout里面执行的就是异步操作的回调函数,等待完成后执行其中的代码。类似于地铁过闸机没网的那个兄弟先站到旁边处理下网络问题,解决了再排队进闸机,这样就不会堵着后续的人。

1.3,回调函数

那如果异步操作完成后我们想要用这个结果做一些事情呢?有人说可以把代码写在异步操作的结果处理里面,但是这样代码一层包一层的,很难复用和维护。

为了解决这个问题,有人提出了回调函数的方案。

其实上文中的setTimeout里面的箭头函数就是一个回调函数,但是不够直观,这里再举个例子。

异步耗时的工作,常见的有以下这些:

定时器
建立网络连接
向文件读写数据

可以拿向文件读写数据来举例,先看如下代码:

const fs = require('fs')
function printA(){console.log(a)
}
function readFile(){fs.readFile('test.txt', (err, data) => {if (err) {console.error(err)return}// data 是二进制类型,需要转换成字符串console.log("读取到的文本内容:",data.toString())a=1})
}
let a=0
readFile()
printA()

从上文可以知道,a虽然被修改了,但它是在读文件操作(异步操作)中被修改的,因为js异步非阻塞的特性,在执行printA中的打印a时,它并没有执行,所以打印出来的a将是0。

那如何把a=1打印出来呢?

我们可以把一个函数作为参数,传给readFile,在readFile函数中执行打印操作。

const fs = require('fs')
function printA(){console.log(a)
}
function readFile(callback){fs.readFile('test.txt', (err, data) => {if (err) {console.error(err)return}// data 是二进制类型,需要转换成字符串console.log("读取到的文本内容:",data.toString())a=1callback()//这就是传进来的回调函数})
}
let a=0
readFile(printA)

这就是回调函数:函数体在完成某种操作后由内向外调用某个外部函数,因为是在函数体内部调用,所以能由内向外获取到相关变量(作用域链)。

简单点说,就是把函数当作参数,传递给异步操作完成之后再执行

1.4,js的事件循环机制

上文说到,js的异步非阻塞机制,那它是如何实现就一个主线程,完美运行所有的同步异步代码呢?

这就需要理解下js的事件循环机制,也就是人们所说的(Event Loop),可以用下图:
请添加图片描述

常见的微任务:

Promises.(then catch finally),process.nextTick, MutationObserver
DOM渲染前触发

这里值得注意的是promise内是同步,而.then后才是微任务。

常见的宏任务:

整体代码script,setTimeout,setInterval ,setImmediate,I/O,UI renderingnew 
DOM渲染后触发

也就是说,遇到异步函操作,还需要判断是宏任务还是微任务,宏任务的话,就把异步操作的结果加入宏任务队列,微任务的话,就加入到微任务队列。
于是,异步到的队列,就由原来的一个事件队列,变成了宏队列和微队列两个,而主线程空了的话,会先去微队列中查找(若在这个过程中,微队列的事件又产生的新的微任务加入队尾,也会在本次循环中进行处理,简而言是就是把每轮循环把微队列搞空),然后再去宏队列中查找(而每次宏任务只执行一个,就又轮到微任务队列)。

第一步: 主线程执行同步任务的同时,把一些异步任务放入‘任务队列’(task queue)中,等待主线程的调用栈为空时,再依次从队列出去任务去执行;
第二步:检测任务队列中的微队列是否为空,若不为空,则取出一个微任务入栈执行;然后继续执行第2步;如果微队列为空,则开始取出宏队列中的一个宏任务执行;
第三步:执行完宏队列中的一个宏任务后,会继续检测微队列是否为空,如果有新插入的任务,这继续执行第二步;如果微队列为空,则继续执行宏队列中的下一个任务,然后再继续循环执行第三步;

示例:

const fs = require('fs')
setTimeout(function callback(){console.log('2')//宏任务Promise.resolve().then(()=>{console.log(6)//宏任务中微任务})
}, 0)
setTimeout(function callback(){console.log('又一个宏任务')//宏任务
}, 0)
new Promise((resolve, reject) => {console.log('3')//同步resolve()
})
.then(res => {console.log('4');//微任务new Promise((resolve, reject) => {console.log('8')//同步fs.readF ile('test.txt', (err, data) => {if (err) {console.error(err)return}// data 是二进制类型,需要转换成字符串console.log("读取到的文本内容:",data.toString())resolve(data.toString())//宏任务})}).then(res => {new Promise((resolve, reject) => {console.log('测试')//同步resolve()}).then(res => {console.log('再测试');//微任务})})
})
console.log('5')//同步

打印的值:

3
5
4
8
2
6
又一个宏任务
读取到的文本内容: 测试文本
测试
再测试

1.5,回调函数的问题和promise的诞生

自此,js已经能够依托事件循环机制,执行所有的任务。但是因为有的任务需要依托异步任务返回的结果,所以我们采用了回调函数的解决方案,那如果有多重异步操作,并且最后的操作依赖于这几次异步操作。那么就容易层层嵌套,带来回调地狱的问题。

如下代码,我创建了三个txt文件,然后需要依次读取文件内容并打印,如果采取回调函数的写法,将是如下样子:

const fs = require('fs')
fs.readFile('test.txt', (err,data) => {let reader1=data.toString()fs.readFile('test1.txt', (err,data) => {let reader2=data.toString()fs.readFile('test2.txt', (err,data) => {let reader3=data.toString()setTimeout(()=>{let result=reader1+reader1+reader3console.log("获得的结果",result)},10)})})
})

fs模块的readFile的第二个参数,便是一个回调函数,为了按照顺序读取文本内容,则需要多层嵌套,这样只有几次倒还好,实际项目中却容易出现多层,导致代码出现回调地狱,难以阅读维护。

为了解决这个问题,promise应运而生。

回调地狱的产生,原因还是回调函数对结果的处理和异步操作终究还是在一起,并没有把分离。而引入promise的最大作用,就是把异步操作的过程和结果做到了分离,可以用promise.then()来获取和处理异步操作的结果。使用promise修改上文的代码:

const fs = require('fs')
function readFile(fileName){return new Promise((resolve,reject)=>{fs.readFile(fileName, (err,data) => {resolve(data.toString())})})
}
let res1,res2,res3
readFile('test.txt')
.then((res)=>{res1=resreturn readFile('test1.txt')
})
.then((res)=>{res2=resreturn readFile('test2.txt')
})
.then((res)=>{res3=resconsole.log("结果",res1+res2+res3)
})

这样一来,就把异步的操作和结果通过.then进行了分离。避免了回调地狱的产生。

二,promise的then方法实现

那promise到底是个啥?为何能有如此效果呢?

2.1,promise的三种状态

pending: 一个promise在resolve或者reject前就处于这个状态。
fulfilled: 一个promise被resolve后就处于fulfilled状态,这个状态不能再改变,而且必须拥有一个不可变的值(value)rejected: 一个promise被reject后就处于rejected状态,这个状态也不能再改变,而且必须拥有一个不可变的拒绝原因(reason)

2.2,promise.then方法

一个promise必须拥有一个then方法来访问他的值或者拒绝原因。then方法有两个参数:

promise.then(onFulfilled, onRejected)

并且这两个参数有以下特征:

1,onFulfilled 和 onRejected 都是可选参数。若不存在则忽略。
2,onFulfilled在promise结束前不可调用,结束后必须被调用,其第一个参数为 promise 的终值value,且调用次数不超过一次。
3,onRejected在被拒绝前不可被调用,拒绝执行后其必须被调用,其第一个参数为 promise 的原因reason,且调用册数不超过一次。

而这个then方法还支持以下特征:

1,then 方法必须返回一个 promise 对象。这样才支持多次then链式调用。
2,then 方法可以被同一个 promise 调用多次。当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调,当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调。

2.3,先写出基本的promise,实现异步操作与结果的分离

我们平时使用promise是这样的:

const p1 = new Promise((resolve, reject) => {console.log('create a promise');setTimeout(()=>{console.log("异步")resolve('成功了');},10)})
const p2 = p1.then((res)=>{console.log(res)
})

所以promise接收一个函数,该函数有两个参数,resolve和reject,并且同步执行这个函数。

// 先定义三个常量表示状态
var PENDING = 'pending';//等待中
var FULFILLED = 'fulfilled';//执行完成
var REJECTED = 'rejected';//拒绝执行function MyPromise(fn) {this.status = PENDING;    // 初始状态为pendingthis.value = null;        // 初始化valuethis.reason = null;       // 初始化reason//同步执行这个函数try {fn(resolve, reject);} catch (error) {reject(error);}
}
exports.MyPromise=MyPromise

可以看到fn(resolve, reject)异步操作执行完毕后,会调用resolve和reject,于是promise里面就需要创建resolve和reject提供调用。

// 这两个方法直接写在构造函数里面
function MyPromise(fn) {// ...省略前面代码...// 存一下this,以便resolve和reject里面访问var that = this;// resolve方法参数是valuefunction resolve(value) {if(that.status === PENDING) {that.status = FULFILLED;that.value = value;}}// reject方法参数是reasonfunction reject(reason) {if(that.status === PENDING) {that.status = REJECTED;that.reason = reason;}}
}

这样一来,就能在执行完promise包裹的异步操作后,调用resolve或者reject,从而改变promise的状态,并且取到结果value或原因reason。

但是,就目前的代码而言,还需要then方法来获取结果并处理。

根据我们前面的分析,then方法可以链式调用,所以他是实例方法,而且规范中的API是promise.then(onFulfilled, onRejected),我们先把架子搭出来:

MyPromise.prototype.then = function(onFulfilled, onRejected) {}

如此,我们在promise中会执行异步操作,异步操作完成后会调用resolve方法,修改promise的状态,并且把结果存在promise的value上。那现在如何在then方法中取得这个结果并执行呢?

首先需要判断该异步操作已经执行完毕(通过promise的状态变成FULFILLED)。异步操作的结果这时候已经存储到promise的value上了,于是只需要then的参数是函数,把这个value传进去即可。

MyPromise.prototype.then = function(onFulfilled, onRejected) {if(this.status === FULFILLED) {onFulfilled(this.value)//将结果传入回调函数}if(this.status === REJECTED) {onRejected(this.reason);}
}

我们现在来试试现在写的promise,写出如下代码:

var MyPromise = require('./test.js').MyPromise;
const p1 = new MyPromise((resolve, reject) => {console.log('create a promise');setTimeout(()=>{console.log("异步")resolve('成功了');},10)})
console.log("直接打印",p1)
setTimeout(()=>{console.log("过两s后",p1)const aaa=p1.then((value)=>{console.log(value)})
},2000)

打印的结果:

请添加图片描述

注意到,这里我们是使用setTimeout包裹了then函数,这意味着现在只是将结果取到了then中进行处理,还不能等待异步完成后再执行then中的代码。

到这里,就可以大致知道promise到底是个啥?

可以把它当作一个异步操作的处理站点。

初始时状态是pending,当执行完毕异步操作(用户定义)后调用resolve/reject,修改状态变成fulfilled/rejected,并且把结果存储在value/reason。

然后then方法判断异步操作是否完成,完成的话,就取value/reason来完成用户接下来的操作(用户定义)。

其实还是利用的回调函数,把promsie当作中转站,将异步操作和结果隔离开来。(异步操作放在promise的入参函数中,结果放value/reason,then方法需要用到了再去这里取)。

请添加图片描述

2.4,then方法的回调函数收集与执行

实际上,我们使用promise的时候,不会像上文那样,用一个setTimeout来包裹,更多时候如下使用:

new Promise(fn).then(onFulfilled, onRejected);

上面代码then是在实例对象一创建好就调用了,这时候fn里面的异步操作可能还没结束,也就是说他的status还是PENDING,这时候肯定不能执行then里面的代码,因为结果还没有存储到value上。

那怎么在异步操作完成后再执行then里面的代码呢?之前说过,resolve和reject方法就是在异步操作完成后调用的,所以可以在这里执行then传入的回调函数。

于是修改我们的promise:

// 构造函数
function MyPromise(fn) {// ...省略其他代码...// 构造函数里面添加两个数组存储成功和失败的回调this.onFulfilledCallbacks = [];this.onRejectedCallbacks = [];function resolve(value) {if(that.status === PENDING) {// ...省略其他代码...// resolve里面将所有成功的回调拿出来执行that.onFulfilledCallbacks.forEach(callback => {callback(that.value);});}}function reject(reason) {if(that.status === PENDING) {// ...省略其他代码...// resolve里面将所有失败的回调拿出来执行that.onRejectedCallbacks.forEach(callback => {callback(that.reason);});}}
}// then方法
MyPromise.prototype.then = function(onFulfilled, onRejected) {// ...省略其他代码...// 如果还是PENDING状态,将回调保存下来if(this.status === PENDING) {this.onFulfilledCallbacks.push(onFulfilled);this.onRejectedCallbacks.push(onRejected);}
}

也就是说,promise.then做的事情,就是把传入的回调函数收集起来,然后在resolve或reject中取出收集好的回调函数来执行。

这样一来。就能保证then中的回调函数在异步操作结束后再执行。到这里我们已经初步实现了promise最核心的异步操作过程与结果的分离。

请添加图片描述

2.5,then的入参和返回

2.5.1,then的入参

之前说过,then方法的入参不是函数则忽略,其实所谓“忽略”并不是什么都不干,对于onFulfilled来说“忽略”就是将value原封不动的返回,对于onRejected来说就是返回reasononRejected因为是错误分支,我们返回reason应该throw一个Error,所以这里需要重写这两个入参:

MyPromise.prototype.then = function(onFulfilled, onRejected) {// 如果onFulfilled不是函数,给一个默认函数,返回valuevar realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value// 如果onRejected不是函数,给一个默认函数,返回reason的Errorvar realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{throw reason}//...省略其他代码
}
2.5.2,then的返回得是promise

then的返回值必须是一个promise(这里使用promise2)。且如果onFulfilled或者onRejected抛出一个异常e,则这个新的promise2必须拒绝执行,并且返回原因e。也就是把onFulfilled和onRejected用try…catch包裹一层。

MyPromise.prototype.then = function(onFulfilled, onRejected) {// 如果onFulfilled不是函数,给一个默认函数,返回valuevar realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value// // 如果onRejected不是函数,给一个默认函数,返回reason的Errorvar realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{throw reason}var that=thisvar promise2 = new MyPromise(function(resolve, reject) {//如果异步已经成功则直接执行回调函数,期间有报错,需要rejectif(that.status === FULFILLED) {try {realOnFulfilled(that.value);} catch (error) {reject(error);} }//如果异步已经失败则直接执行回调函数,期间有报错,需要rejectif(that.status === REJECTED) { try {realOnRejected(that.reason);} catch (error) {reject(error);}}// 如果还是PENDING状态,将回调保存下来,为了捕获回调中的错误,需要try...catch包裹一层if(that.status === PENDING) {that.onFulfilledCallbacks.push(function() {try {realOnFulfilled(that.value);} catch (error) {reject(error);}});that.onRejectedCallbacks.push(function() {try {realOnRejected(that.reason);} catch (error) {reject(error);}});}})return promise2
}
2.5.3,then的入参默认是函数

而如果onFulfilled不是函数且promise1成功执行,那么promise2必须成功执行并且返回相同的值。这时候,就需要直接透传resolve(this.value)给promise2了。

如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的原因。

MyPromise.prototype.then = function(onFulfilled, onRejected) {// 如果onFulfilled不是函数,给一个默认函数,返回valuevar realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value// // 如果onRejected不是函数,给一个默认函数,返回reason的Errorvar realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{throw reason}var that=this//如果异步已经成功则直接执行回调函数,期间有报错,需要rejectif(this.status === FULFILLED) {var promise2 = new MyPromise(function(resolve, reject) {try {//then方法传入非函数,且成功执行,则把结果传给promise2if (typeof onFulfilled !== 'function') {resolve(that.value);} else {realOnFulfilled(that.value);resolve(that.value);}} catch (error) {reject(error);} });return promise2;}//如果异步已经失败则直接执行回调函数,期间有报错,需要rejectif(this.status === REJECTED) {        var promise2 = new MyPromise(function(resolve, reject) {try {//then方法传入非函数,且成功执行,则把结果传给promise2if (typeof onRejected !== 'function') {reject(that.reason);} else {realOnRejected(that.reason);//promise1的onRejected执行成功后,promise2应该被resolve,这样promise2的状态才会是fulfilledresolve();}} catch (error) {reject(error);}});return promise2;}// 如果还是PENDING状态,将回调保存下来,为了捕获回调中的错误,需要try...catch包裹一层if(this.status === PENDING) {var promise2 = new MyPromise(function(resolve, reject) {that.onFulfilledCallbacks.push(function() {try {//如果传入的不是函数,则透传结果给promise2if (typeof onFulfilled !== 'function') {resolve(that.value);} else {realOnFulfilled(that.value);resolve(that.value);}} catch (error) {reject(error);}});that.onRejectedCallbacks.push(function() {try {//如果传入的不是函数,则透传结果给promise2if (typeof onRejected !== 'function') {reject(that.reason);} else {realOnRejected(that.reason);resolve()}} catch (error) {reject(error);}});});return promise2;}}
2.5.4,如果传入then的回调函数有返回值

如果 onFulfilled 或者 onRejected 返回一个值 x ,需要区分x的类别,运行下面的 Promise 解决过程

MyPromise.prototype.then = function(onFulfilled, onRejected) {// 如果onFulfilled不是函数,给一个默认函数,返回valuevar realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value// // 如果onRejected不是函数,给一个默认函数,返回reason的Errorvar realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{throw reason}var that=thisvar promise2 = new MyPromise(function(resolve, reject) {//如果异步已经成功则直接执行回调函数,期间有报错,需要rejectif(that.status === FULFILLED) {try {if (typeof onFulfilled !== 'function') {resolve(that.value);} else {var x = realOnFulfilled(that.value);resolvePromise(promise2, x, resolve, reject);}} catch (error) {reject(error);} }//如果异步已经失败则直接执行回调函数,期间有报错,需要rejectif(that.status === REJECTED) { try {if (typeof onRejected !== 'function') {reject(that.reason);} else {var x = realOnRejected(that.reason);resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程}} catch (error) {reject(error);}}// 如果还是PENDING状态,将回调保存下来,为了捕获回调中的错误,需要try...catch包裹一层if(that.status === PENDING) {that.onFulfilledCallbacks.push(function() {try {if (typeof onFulfilled !== 'function') {resolve(that.value);} else {var x = realOnFulfilled(that.value);resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程}} catch (error) {reject(error);}});that.onRejectedCallbacks.push(function() {try {if (typeof onRejected !== 'function') {reject(that.reason);} else {var x = realOnRejected(that.reason);resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程}} catch (error) {reject(error);}});}})return promise2
}

在规范中还有一条:onFulfilledonRejected 只有在执行环境堆栈仅包含平台代码时才可被调用。这一条的意思是实践中要确保 onFulfilledonRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。所以在我们执行onFulfilledonRejected的时候都应该包到setTimeout里面去。

按照我个人的理解,这里包裹个setTimeout是为了让resolvePromise(promise2, x, resolve, reject);这里传入的promise2不是undefined。

于是then方法变成:

MyPromise.prototype.then = function(onFulfilled, onRejected) {// 如果onFulfilled不是函数,给一个默认函数,返回valuevar realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value// // 如果onRejected不是函数,给一个默认函数,返回reason的Errorvar realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{throw reason}var that=thisvar promise2 = new MyPromise(function(resolve, reject) {//如果异步已经成功则直接执行回调函数,期间有报错,需要rejectif(that.status === FULFILLED) {setTimeout(function() {try {if (typeof onFulfilled !== 'function') {resolve(that.value);} else {var x = realOnFulfilled(that.value);resolvePromise(promise2, x, resolve, reject);}} catch (error) {reject(error);} },0)}//如果异步已经失败则直接执行回调函数,期间有报错,需要rejectif(that.status === REJECTED) {  setTimeout(function() {try {if (typeof onRejected !== 'function') {reject(that.reason);} else {var x = realOnRejected(that.reason);resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程}} catch (error) {reject(error);}},0)}// 如果还是PENDING状态,将回调保存下来,为了捕获回调中的错误,需要try...catch包裹一层if(that.status === PENDING) {that.onFulfilledCallbacks.push(function() {setTimeout(function () {try {if (typeof onFulfilled !== 'function') {resolve(that.value);} else {var x = realOnFulfilled(that.value);resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程}} catch (error) {reject(error);}},0)});that.onRejectedCallbacks.push(function() {setTimeout(function () {try {if (typeof onRejected !== 'function') {reject(that.reason);} else {var x = realOnRejected(that.reason);resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程}} catch (error) {reject(error);}},0)});}})return promise2
}

2.6,Promise 解决过程

从上文看,Promise 解决过程是为了处理then传入的函数带返回值的情况。于是针对不同的返回值,需要有不同的处理方案。实现的原则就是一点:让then方法返回的promise2的值接收then入参函数的返回值。

所以resolvePromise这样写:

resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程

把promise2传入,把then入参函数的返回x(可能没有)传入,然后传入promise2的resolve和reject,利用它来修改promise2的状态和值。

2.6.1,如果promise和x引用同一个对象,则用TypeError作为原因拒绝(reject)promise。
function resolvePromise(promise, x, resolve, reject) {// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise// 这是为了防止死循环if (promise === x) {return reject(new TypeError('The promise and the return value are the same'));}
}
2.6.2,如果then入参函数返回的是promise

如果x是一个promise,则需要递归逐层展开返回的promise取得结果y,并且把promise2和它的resolve和reject传入,只有这样,promise2才能得到最后的结果:

function resolvePromise(promise, x, resolve, reject) {// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise// 这是为了防止死循环if (promise === x) {return reject(new TypeError('The promise and the return value are the same'));}if (x instanceof MyPromise) {// 如果 x 为 Promise ,则让then返回的 promise 接受 x 的状态和结果// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析yx.then(function (y) {//注意这里传入的就是入参promise,也就是修改这个promise的状态和结果,简单写就是 resolve(y)resolvePromise(promise, y, resolve, reject);}, reject);}
}
2.6.3,如果x是个对象或者方法,这里处理的是类promise对象,即具备then方法的对象。
2.3.3另外,如果x是个对象或者方法,这里处理的是类promise对象,即具备then方法的对象。2.3.3.1 让x作为x.then2.3.3.2 如果取回的x.then属性的结果为一个异常e,用e作为原因reject promise2.3.3.3 如果then是一个方法,把x当作this来调用它, 第一个参数为 resolvePromise,第二个参数为rejectPromise,其中:2.3.3.3.1 如果/当 resolvePromise被一个值y调用,运行 [[Resolve]](promise, y)2.3.3.3.2 如果/当 rejectPromise被一个原因r调用,用r拒绝(reject)promise2.3.3.3.3 如果resolvePromise和 rejectPromise都被调用,或者对同一个参数进行多次调用,第一次调用执行,任何进一步的调用都被忽略2.3.3.3.4 如果调用then抛出一个异常e,2.3.3.3.4.1 如果resolvePromise或 rejectPromise已被调用,忽略。2.3.3.3.4.2 或者, 用e作为reason拒绝(reject)promise2.3.3.4 如果then不是一个函数,用x完成(fulfill)promise

这里规范中讲得很复杂,其实就是为了处理返回的是类promsie(thenable)对象。

如下代码:

var thenableA = {name: 'thenableA',then: function (resolve, reject) {console.log(`I'am ${this.name}`);resolve(this.name)}
}
var p1=new MyPromise((resolve, reject) => {console.log('create promise1');resolve(1)
})
p1.then((res) => {return thenableA
})

打印出来就是:

create promise1
I'am thenableA

具体的then方法实现的内容,就是把thenable对象像promise一样展开,执行其中的then方法,同样是让then方法返回的promise2的值接收最终的值。

function resolvePromise(promise, x, resolve, reject) {//...其他代码// 如果 x 为对象或者函数,即可能是thenable的对象/函数else if (x  &&(typeof x === 'object' || typeof x === 'function')) {var called = false;try {// 把 x.then 赋值给 then var then = x.then;// 如果 then 是函数,则返回的其实是类promise(thenable)对象,需要展开执行其中的then方法if (typeof then === 'function') {then.call(x,function (y) {if (called) return;called = true;resolvePromise(promise, y, resolve, reject);//把它的then方法执行掉,并且修改promise2的值和状态},function (r) {if (called) return;called = true;reject(r);});} else {// 如果 then 不是函数(then返回的不是thenable对象),以 x 为参数执行 promiseif (called) return;called = true;resolve(x);}} catch (error) {// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise,return是为了不执行后续的代码if (called) return;called = true;return reject(error);}
}

例如上文代码:

var thenableA = {name: 'thenableA',then: function (resolve, reject) {console.log(`I'am ${this.name}`);resolve(this.name)}
}
var p1=new MyPromise((resolve, reject) => {console.log('create promise1');resolve(1)
})
var p2=p1.then((res) => {return thenableA
})setTimeout(()=>{console.log("+++++",p2)
},0)

执行之后打印的是:

create promise1
I'am thenableA
+++++ MyPromise {status: 'fulfilled',value: 'thenableA',reason: null,onFulfilledCallbacks: [],onRejectedCallbacks: []
}

可以看到thenableA的then方法被执行,并且改变了promise2的值,于是p2的value变成了’thenableA’。

2.6.4,返回的x不是函数也不是对象,甚至没有返回(x为undefined)

如果 x既不是对象也不是函数,用x完成(fulfill)promise,也就是直接resolve(x)即可。

三,验证promiseA+规范

在我们写好的MyPromsie中添加如下代码:

MyPromise.deferred = function() {var result = {};result.promise = new MyPromise(function(resolve, reject){result.resolve = resolve;result.reject = reject;});return result;
}
module.exports = MyPromise//记得这一步必须这样导出,不然会报错。

然后项目目录安装测试库:

npm i promises-aplus-tests -g

于是我们的手手写promise完整内容如下:


// 先定义三个常量表示状态
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';function MyPromise(fn) {this.status = PENDING;    // 初始状态为pendingthis.value = null;        // 初始化valuethis.reason = null;       // 初始化reason// 构造函数里面添加两个数组存储成功和失败的回调this.onFulfilledCallbacks = [];this.onRejectedCallbacks = [];// 存一下this,以便resolve和reject里面访问var that = this;// resolve方法参数是valuefunction resolve(value) {if(that.status === PENDING) {that.status = FULFILLED;that.value = value;// resolve里面将所有成功的回调拿出来执行that.onFulfilledCallbacks.forEach(callback => {callback(that.value);});}}// reject方法参数是reasonfunction reject(reason) {if(that.status === PENDING) {that.status = REJECTED;that.reason = reason;// resolve里面将所有失败的回调拿出来执行that.onRejectedCallbacks.forEach(callback => {callback(that.reason);});}}try {fn(resolve, reject);} catch (error) {reject(error);}
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {// 如果onFulfilled不是函数,给一个默认函数,返回valuevar realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value// // 如果onRejected不是函数,给一个默认函数,返回reason的Errorvar realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{throw reason}var that=thisvar promise2 = new MyPromise(function(resolve, reject) {//如果异步已经成功则直接执行回调函数,期间有报错,需要rejectif(that.status === FULFILLED) {setTimeout(function() {try {if (typeof onFulfilled !== 'function') {resolve(that.value);} else {var x = realOnFulfilled(that.value);resolvePromise(promise2, x, resolve, reject);}} catch (error) {reject(error);} },0)}//如果异步已经失败则直接执行回调函数,期间有报错,需要rejectif(that.status === REJECTED) {  setTimeout(function() {try {if (typeof onRejected !== 'function') {reject(that.reason);} else {var x = realOnRejected(that.reason);resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程}} catch (error) {reject(error);}},0)}// 如果还是PENDING状态,将回调保存下来,为了捕获回调中的错误,需要try...catch包裹一层if(that.status === PENDING) {that.onFulfilledCallbacks.push(function() {setTimeout(function () {try {if (typeof onFulfilled !== 'function') {resolve(that.value);} else {var x = realOnFulfilled(that.value);resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程}} catch (error) {reject(error);}},0)});that.onRejectedCallbacks.push(function() {setTimeout(function () {try {if (typeof onRejected !== 'function') {reject(that.reason);} else {var x = realOnRejected(that.reason);resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程}} catch (error) {reject(error);}},0)});}})return promise2
}function resolvePromise(promise, x, resolve, reject) {// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise// 这是为了防止死循环if (promise === x) {return reject(new TypeError('The promise and the return value are the same'));}if (x instanceof MyPromise) {console.log("是MyPromise")// 如果 x 为 Promise ,则让then返回的 promise 接受 x 的状态和结果// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析yx.then(function (y) {//注意这里传入的就是入参promise,也就是修改这个promise的状态和结果,简单写就是 resolve(y)resolvePromise(promise, y, resolve, reject);}, reject);}// 如果 x 为对象或者函数else if (x  &&(typeof x === 'object' || typeof x === 'function')) {var called = false;try {// 把 x.then 赋值给 then var then = x.then;// 如果 then 是函数,则返回的其实是类promise(thenable)对象,需要展开执行其中的then方法if (typeof then === 'function') {then.call(x,function (y) {if (called) return;called = true;resolvePromise(promise, y, resolve, reject);},function (r) {if (called) return;called = true;reject(r);});} else {// 如果 then 不是函数(then返回的不是thenable对象),以 x 为参数执行 promiseif (called) return;called = true;resolve(x);}} catch (error) {// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise,return是为了不执行后续的代码if (called) return;called = true;return reject(error);}} else {// 如果 x 不为对象或者函数,以 x 为参数执行 promise,没有返回时也执行这个resolve(x);}
}MyPromise.deferred = function() {var result = {};result.promise = new MyPromise(function(resolve, reject){result.resolve = resolve;result.reject = reject;});return result;
}
module.exports = MyPromise

项目根目录执行测试:

promises-aplus-tests test.js(test.js是我的文件名)

可以看到,通过了全量的测试用例。

请添加图片描述

四,promise.resolve的实现

它的作用是将现有对象转为Promise对象。

即,如果是promise则直接返回,如果不是则返回值为这个的promise。

MyPromise.resolve = function(parameter) {if(parameter instanceof MyPromise) {return parameter;}return new MyPromise(function(resolve) {resolve(parameter);});
}

即:

var promise1 = new MyPromise((resolve, reject) => {setTimeout(()=>{resolve(2)},10)
})
var p2=MyPromise.resolve(promise1)
p2.then((res)=>{console.log(res)//打印2
})

五,Promise.reject的实现

返回一个新的Promise实例,该实例的状态为rejected。Promise.reject方法的参数reason,会被传递给实例的回调函数。

MyPromise.reject = function(reason) {return new MyPromise(function(resolve, reject) {reject(reason);});
}

六,Promise.all的实现

该方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。它接受一个数组作为参数,p1p2p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。当p1, p2, p3全部resolve,外层的promise才resolve,有任何一个reject,外层的promise都reject。

const p = Promise.all([p1, p2, p3]);
MyPromise.all = function(promiseList) {var resPromise = new MyPromise(function(resolve, reject) {var count = 0;var result = [];var length = promiseList.length;if(length === 0) {return resolve(result);}promiseList.forEach(function(promise, index) {MyPromise.resolve(promise).then(function(value){count++;result[index] = value;if(count === length) {//全部执行完毕后才resolve结果数组出去resolve(result);}}, function(reason){reject(reason);});});});return resPromise;
}

使用的示例:

var p1 = new MyPromise((resolve, reject) => {setTimeout(()=>{resolve(1)},10)
})
var p2 = new MyPromise((resolve, reject) => {setTimeout(()=>{resolve(2)},0)
})
var p3 = new MyPromise((resolve, reject) => {setTimeout(()=>{resolve(3)},20)
})var result=MyPromise.all([p1,p2,p3])
setTimeout(()=>{console.log(result)//打印返回的所有结果
},1000)
result.then((res)=>{console.log(res)
})

得到的结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z0tyj5fd-1680622612470)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230329225315698.png)]

七,Promise.race方法

和promise.all的用法一样,传入一个promsie数组,不同的是,promise.race是一旦有一个promise执行完毕后,就返回结果。

MyPromise.race = function(promiseList) {var resPromise = new MyPromise(function(resolve, reject) {var length = promiseList.length;if(length === 0) {return resolve();} else {for(var i = 0; i < length; i++) {//哪个先成功就直接resolve结果了,因为return了,就不执行后续的结果了,不用foreach是因为它无法returnMyPromise.resolve(promiseList[i]).then(function(value) {return resolve(value);}, function(reason) {return reject(reason);});}}});return resPromise;
}

使用的示例:

var p1 = new MyPromise((resolve, reject) => {setTimeout(()=>{resolve(1)},10)
})
var p2 = new MyPromise((resolve, reject) => {setTimeout(()=>{resolve(2)},0)
})
var p3 = new MyPromise((resolve, reject) => {setTimeout(()=>{resolve(3)},20)
})var result=MyPromise.race([p1,p2,p3])
setTimeout(()=>{console.log(result)//打印返回的所有结果
},1000)
result.then((res)=>{console.log(res)
})

八,Promise.prototype.catch

Promise.prototype.catch方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

MyPromise.prototype.catch = function(onRejected) {this.then(null, onRejected);
}

九,总结

9.1,promise做到异步操作和结果的分离

这一点是promise存在的最大价值体现。它的实现,其实就是让then方法传入的函数在异步操作后执行即可。这里为了方便描述,我只说成功的情况。

一种情况是在调用then方法的时候,异步操作还未完成(promise的状态是pedding)。大致的流程如下:

【这里本来是动图的,但是csdn不允许上传这么大的动图,,,具体的可以看我公众号吧,嘻嘻,我最近开始写公众号啦!撒花!】

另一种情况是,在调用then方法的时候,异步操作已经完成(promise的状态已经变成fulfilled)。这时候收集器就不用收集回调函数了,而是可以直接执行,大致的流程如下:

【这里本来是动图的,但是csdn不允许上传这么大的动图,,,具体的可以看我公众号吧,嘻嘻,我最近开始写公众号啦!撒花!】

9.2,then能链式调用是因为then返回是个新的promise

我们为了解决回调地狱,使用promise时,会进行链式调用。这是因为then方法返回一个新的promise。

并且这个新的promise倾向于返回最后的处理结果

这句话怎么理解呢?还是只说成功情况的处理。

1,then不传入函数参数时,透传promise的处理结果。
2,then传入的函数参数没有返回时,也就是返回值为undefined,那么返回的新promise的value值是undefined。
3,then传入的函数参数有返回值,且返回值是promise,则取得这个promse最后的结果,传递给它返回的promise。
4,then传入的函数参数有返回值,且返回值是thenable对象,和promise差不多,就是执行thenable对象的then方法,取得这个promise最后的结果,传递给它返回的promise。
5,then传入的函数参数有返回值,且返回值是正常数据,则传递给它返回的promise。

相关文章:

从异步到promise

一&#xff0c;背景 1.1&#xff0c;js的单线程 这一切&#xff0c;要从js诞生之初说起&#xff0c;因为js是单线程的语言。 js单线程原因&#xff1a;作为浏览器脚本语言&#xff0c;JavaScript的主要用途是与用户互动&#xff0c;以及操作DOM。这决定了它只能是单线程&…...

Linux系统中进行JDK环境的部署

一、为什么需要部署JDK。 JDK&#xff1a;Java Development Kit&#xff0c;是用于Java语言开发的环境。 部署JDK不需要懂得Java语言&#xff0c;只需要掌握Linux相关命令即可。 二、部署版本与环境。 系统&#xff1a;安装在VMware环境下的CentOS7.6&#xff1b; JDK版本&a…...

Leetcode.1033 移动石子直到连续

题目链接 Leetcode.1033 移动石子直到连续 Rating &#xff1a; 1421 题目描述 三枚石子放置在数轴上&#xff0c;位置分别为 a&#xff0c;b&#xff0c;c。 每一回合&#xff0c;你可以从两端之一拿起一枚石子&#xff08;位置最大或最小&#xff09;&#xff0c;并将其放入…...

【Java】在SpringBoot中使用事务注解(@Transactional)时需要注意的点

在SpringBoot中使用事务注解&#xff08;Transactional&#xff09;时需要注意的点Transactional是什么使用事务注解&#xff08;Transactional&#xff09;时需要注意的点Transactional是什么 Transactional是Spring框架提供的一个注解&#xff0c;用于声明事务边界和配置事务…...

找到序列最高位的1和最高位的0并输出位置

前言&#xff1a; 该题为睿思芯科笔试题&#xff0c;笔试时长20分钟。 题目描述 接口如下&#xff1a; module first_1_and_0#(parameter WIDTH 8 )(input [WIDTH-1:0] data_in ,input target ,output exist ,outpu…...

面试总结sdiugiho

一、进程与线程的区别 进程&#xff1a; 一个在内存中运行的应用程序&#xff0c;每个进程都有自己独立的一块内存空间&#xff0c;一个进程可以有多个线程&#xff1b; windows 任务管理器中 一个 exe 就是一个进程。 线程&#xff1a; 进程中的一个执行任务&#xff08;控制…...

WIN10無法再使用 IE 瀏覽器打开网页解决办法

修改 Registry&#xff08;只適用 Win10&#xff09; 微軟已於 2023 年 2 月 14 日永久停用 Internet Explorer&#xff0c;會透過 Edge 的更新讓使用者開啟 IE 時自動導向 Edge&#xff0c;其餘如工作列上的圖示&#xff0c;使用的方法則是透過「品質更新」的 B 更新來達成&am…...

搭建SpringBoot和Mysql Demo

1. 引言 在上一篇文章中&#xff0c;介绍了如何搭建一个SpringBoot项目&#xff1b;本篇文章&#xff0c;在上一篇文章的基础上&#xff0c;接着介绍下怎样实现SpringBoot和MySQL的整合。在后端开发中&#xff0c;数据库开发是绕不开的话题&#xff0c;开发中很多的时间都是在…...

晶振03——晶振烧坏的原因

晶振03——晶振烧坏的原因 首先要清楚的一件事情是&#xff1a;晶振分为无源晶振与有源晶振两大类。基于这两类晶振的内部结构与工作原理的差异&#xff0c;晶振被烧坏的情况也要分为两大类&#xff1a; 针对无源晶振被烧坏的情况有以下两点&#xff1a; 1、手焊操作不当 假…...

项目管理的难点

一、项目团队建设 建设一支高效的项目团队&#xff0c;明确团队队员的职责是项目经理进行项目管理的首要条件&#xff0c;也是项目目标能否实现的关键。 1.1 学会放权 任何人都不能掌握所有的知识和技能&#xff0c;要敢于相信别人&#xff0c;让别人去做。 放权就要选择最…...

day22 ● 235. 二叉搜索树的最近公共祖先 ● 701.二叉搜索树中的插入操作 ● 450.删除二叉搜索树中的节点

问题&#xff1a; ● 235. 二叉搜索树的最近公共祖先 ● 701.二叉搜索树中的插入操作 ● 450.删除二叉搜索树中的节点 首先&#xff0c;二叉搜索树是一种常见的数据结构&#xff0c;它具有以下特点&#xff1a; 每个节点最多有两个子节点&#xff0c;分别为左子节点和右子节…...

ChatGPT 这个风口,普通人怎么抓住?

最近在测试ChatGPT不同领域的变现玩法&#xff0c;有一些已经初见成效&#xff0c;接下来会慢慢分享出来。 今天先给大家分享一个&#xff0c;看完就能直接上手的暴力引流玩法。 所需工具&#xff1a; 1&#xff09;ChatGPT&#xff08;最好是plus版&#xff0c;需要保证快速…...

Python代码规范:企业级代码静态扫描-代码规范、逻辑、语法、安全检查,以及代码规范自动编排(2)

本篇将总结实际项目开发中Python代码规范检查、自动编排的一些工具&#xff0c;特点&#xff0c;使用方法&#xff0c;以及如何在Pycharm中集成这些工具&#xff0c;如autoflake、yapf、black、isort、autopep8代码规范和自动编排工具。上一篇总结的pylint、pyproject-flake8、…...

acme.sh从 letsencrypt 生成SSL免费证书并自动更新证书

acme.sh 实现了 acme 协议, 可以从 letsencrypt 生成免费的证书 ACME 协议: Automatic Certificate Management Environment 自动化证书管理环境 文档: github: https://github.com/acmesh-official/acme.shgitee: https://gitee.com/neilpang/acme.sh中文文档: https://git…...

基于html+css的evenly布局

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…...

【从零开始学习 UVM】10.5、UVM TLM —— UVM TLM Blocking Get Port

文章目录 UVM TLM Get Port Example1. 创建一个发送方类,其端口类型为 uvm_blocking_get_imp3. 创建接收器类,等待 get 方法。4. 在更高层次上连接端口及其实现Get端口阻塞行为任何组件都可以通过 TLM get 端口请求从另一个组件接收事务。发送组件应定义获取端口的实现。该实…...

English Learning - L2 第 10 次小组纠音 辅音 [m] [n] [ŋ] 半元音 [w] [j] 2023.3.29 周三

English Learning - L2 第 10 次小组纠音 辅音 [m] [n] [ŋ] [w] [j] 2023.3.29 周三共性问题more Autumn [ɔː] 舌位偏前gone evening 前后鼻音不分Hes proud of this name 双元音缺乏滑动感bank thing 中的后鼻音发成前鼻音week what yolk 元音 [iː] [ɒ] 舌位偏前 [əʊ] …...

从零开始实现一个C++高性能服务器框架----环境变量模块

此项目是根据sylar框架实现&#xff0c;是从零开始重写sylar&#xff0c;也是对sylar丰富与完善 项目地址&#xff1a;https://gitee.com/lzhiqiang1999/server-framework 简介 项目介绍&#xff1a;实现了一个基于协程的服务器框架&#xff0c;支持多线程、多协程协同调度&am…...

git常用命令的解释

解释git add . git add . 命令用于将当前目录下的所有文件添加到 Git 仓库的暂存区中。这个命令通常用于刚刚打开一个 Git 仓库时&#xff0c;或者用于将本地文件更新到远程仓库时。 具体来说&#xff0c;git add . 命令会将当前目录下的所有文件添加到 Git 仓库的暂存区中&am…...

DNS和CDN的区别与联系

现在互联网用户很多不能理解CDN和DNS之间的关系&#xff0c;他们之间到底有什么区别。对于这两者永远处于模糊的概念。其实二者是相辅相成的&#xff0c;二者搭配起来能使网站更加安全&#xff0c;快速。 用户访问未使用CDN缓存网站的过程为: 1、用户向浏览器提供要访问的域名…...

Java基础知识 | 常见面试题(中):面向对象基础

撰写成一问一答的形式&#xff0c;每次回答都默写&#xff0c;对比参考答案后&#xff0c;再默写出更恰当的答案。 相关内容 Java基础知识 | 常见面试题&#xff08;上&#xff09;&#xff1a;基础概念和常识 Java基础知识 | 常见面试题&#xff08;上&#xff09;&#xff1a…...

勒索软件正在从 Windows 转向 Linux

听说勒索软件正在从 Windows 转向 Linux了勒索软件正在从 Windows 转向 Linux 最近几周&#xff0c;黑客们一直在对 Linux 企业网络部署 IceFire 勒索软件&#xff0c;这是一个值得注意的转变&#xff0c;因为它曾经是一个只针对 Windows 的恶意软件。与 Windows 相比&#xf…...

信息系统项目管理师 第11章 项目成本管理

1.管理基础 1.重要性和意义 项目管理主要受范围、时间、成本、质量的约束,项目成本管理就是要确保在批准的预算内完成项目。 如果项目建设的实际成本远远超出批准的投资预算,就很容易造成成本失控。 1.对工程项目认识不足。 2.组织制度不健全。 3.方法问题 4.技术的制约 5.需…...

XML 简介

文章目录一、XML 简介二、XML 用途总结一、XML 简介 XML 被设计用来传输和存储数据。 HTML 被设计用来显示数据。 XML 指可扩展标记语言&#xff08;eXtensible Markup Language&#xff09;。 可扩展标记语言&#xff08;英语&#xff1a;Extensible Markup Language&#xf…...

ERP:华为杀入,金蝶们打颤?

配图来自Canva可画 近期&#xff0c;华为官方透露将在4月份推出自研MetaERP管理系统&#xff0c;引来不少媒体和业内人士的围观&#xff0c;紧接着关于华为“进军ERP市场”的解读更是不胫而走&#xff0c;所谓一石激起千层浪&#xff0c;此说法一出&#xff0c;直接导致了金蝶…...

Linux——总复习1

1.要注意自己处于当前那个目录位置。 2.将file1的前五行/后三行重定向、附加到file2【输出重定向】 head -5 file1 > file2 tail -3 file1 >> file2 3.ls与cat区别 ls:列出目录的目录内容&#xff0c;未指定目录&#xff0c;则列出当前工作目录的内容 -l:查…...

控制SQL*PLUS的环境和数据字典简介

可以通过使用SET命令来设置SQL*PLUS的环境变量&#xff0c;从而达到控制SQL*PLUS 环境的目的。 SET命令的格式如下&#xff1a; SET 环境变量 变量的值 可以通过使用SHOW命令来显示SQL*PLUS环境变量的配置。SHOW 命令的格式如下&#xff1a; SHOW 环境变量|ALL 下面用一个…...

Chapter11.3:MATLAB_SIMULINK在离散系统中的应用

该系列博客主要讲述Matlab软件在自动控制方面的应用&#xff0c;如无自动控制理论基础&#xff0c;请先学习自动控制系列博文&#xff0c;该系列博客不再详细讲解自动控制理论知识。 自动控制理论基础相关链接&#xff1a;https://blog.csdn.net/qq_39032096/category_10287468…...

过滤器Filter

什么是Filter&#xff1f; Filter表示过滤器&#xff0c;是JavaWeb三大组件&#xff08;Servlet、FIlter、Listener&#xff09;之一。过滤器可以把对资源的请求拦截下来&#xff0c;总而实现一些特殊的功能 使用过滤器后&#xff0c;要想访问web服务器上的资源&#xff0c;必须…...

MySQL数据同步ES的常用思路和方法

文章目录 1.同步双写2.异步双写3.定时任务4.数据订阅大家应该都在各种电商网站检索过商品,检索商品一般都是通过什么实现呢?搜索引擎Elasticsearch。 那么问题来了,商品上架,数据一般写入到MySQL的数据库中,那么用于检索的数据又是怎么同步到Elasticsearch的呢? 1.同步双…...