当前位置: 首页 > 编程日记 > 正文

根据PromiseA+规范实现Promise

Promise是ES6出现的一个异步编程的一个解决方案,改善了以往回调函数的回调地狱(虽然写起来也挺像的)。不会Promise的可以移步阮一峰的Promise,这里讲的非常清晰。

就现在的发展情况而言,Promise这种解决方案频繁的在我们的代码中出现,当然也成为面试必问的一项,可想而知,理解Promise的实现是非常重要的一点。

本文主要是通过PromiseA+这个规范来一步一步的实现Promise。不了解规范可以看一下下面的规范。

  1. 中文版规范
  2. 英文版规范

从构造函数开始

Promise是一个构造函数,接受一个函数作为参数。从真正的Promise中我们知道,如果接受的不是函数,会报TypeError。

于是我们实现下。

var MyPromise = function(fn){if(typeof fn !== 'function'){throw new TypeError('Promise resolver undefined is not a function');}
}
复制代码

状态

Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。开始默认状态为pending,只有异步操作的结果才能决定当前是哪一种状态,状态一旦改变,就无法再更改。函数参数接受两个函数作为参数,用于改变状态。我们来实现下。

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
var MyPromise = function(fn){const _this = this;_this.state = PENDING;_this.value = null;     //Promise 的初始终值_this.resolve = function(value){if(_this.state === PENDING){_this.state = RESOLVED;_this.value =value;   //终值}}_this.reject = function(value){if(_this.state === PENDING){_this.state = REJECTED;_this.value =value; //据因}}
}
复制代码

参数函数自执行

Promise构造函数中的函数参数是自动执行的,并且我们要规避一个问题:当传入的参数抛出异常的情况,如果有直接转成rejected状态。在构造函数中最下面添加代码

try{fn(_this.resolve,_this.reject);
}catch(e){_this.reject(e);
}
复制代码

简单实现then方法

then方法接受两个函数作为参数,同时两个参数都是可选的。如果两个参数都不写的话就形成了透传的作用,留给后面的then方法来接参数。then中的参数方法是在promise状态确认之后,并且在状态未确认之前可能会有多个then方法注册,为了保证resolve函数和reject函数的调用时机需要把构造函数中resolve和reject方法修改成异步,于是我们改下MyPromise构造函数。

//添加两个保存回调函数的数组。
_this.resolveCallbacks = [];    //用于保存then方法中resolve
_this.rejectCallbacks = [];     //用于保存then方法中reject
//修改下resolve和reject方法
_this.resolve = function(value){//判断参数是不是一个promiseif(value instanceof MyPromise){//如果value是一个promise 递归执行   return value.then(_this.resolve,_this.reject);}//异步执行 保证执行顺序setTimeout(() => {if(_this.state === PENDING){_this.state = RESOLVED;_this.value =value;   //终值_this.resolveCallbacks.forEach(cb=>cb(_this.value));}},0)
}
_this.reject = function(value){//异步执行 保证执行顺序setTimeout(()=>{if(_this.state === PENDING){_this.state = REJECTED;_this.value =value; //据因_this.rejectCallbacks.forEach(cb=>(_this.value));}  },)
}
复制代码

我们再来写一下then方法

MyPromise.prototype.then = function(onFulfilled,onRejected){const _self = this;//规范2.2.7.3 /2.2.7.4//如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值//如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因//如果参数不是函数就忽略,同时实现透传  new Promise().then().then(x=>x)onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x;onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r};if(_self.state ===PENDING){_self.resolveCallbacks.push(onFulfilled);_self.rejectCallbacks.push(onRejected);}if(_self.state === RESOLVED){onFulfilled(_self.value);}if(_self.state === REJECTED){onRejected(_self.value);}
}
复制代码

then方法必须返回一个Promise

规范2.27 Promise的then方法返回一个新的promise。我们来修改一下then方法(先把多余的注释去掉,加上新的注释,最后面会留一个完整的)。

MyPromise.prototype.then = function(onFulfilled,onRejected){const _self = this;//规范2.2.7//promise 的then方法返回一个新的promisevar promise2;onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x;onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r};if(_self.state ===PENDING){return new MyPromise(function(resolve,reject){_self.resolveCallbacks.push(onFulfilled);_self.rejectCallbacks.push(onRejected);})}if(_self.state === RESOLVED){return new MyPromise(function(resolve,reject){onFulfilled(_self.value);})}if(_self.state === REJECTED){return new MyPromise(function(resolve,reject){onRejected(_self.value);})}
}
复制代码

then方法异常处理及兼容调用

在promiseA+规范中的第2.2.7条中我们可以看到这样一些东西。

  1. 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
  2. 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e

解释一下Promise解决过程:解决过程是为了让不同的Promise都可以兼容的使用。比如说jQuery的Promise。

MyPromise.prototype.then = function(onFulfilled,onRejected){const _self = this;var promise2;onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x;onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r};if(_self.state ===PENDING){return new MyPromise(function(resolve,reject){_self.resolveCallbacks.push(function(){//规范2.2.7.2//如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 etry{var x = onFulfilled(_self.value);//规范2.2.7.1resolutionProcedure(promise2,x,resolve,reject);}catch(e){reject(e);}});_self.rejectCallbacks.push(function(){//规范2.2.7.2try{var x = onRejected(_self.value);//规范2.2.7.1resolutionProcedure(promise2,x,resolve,reject);}catch(e){reject(e);}});})}if(_self.state === RESOLVED){return new MyPromise(function(resolve,reject){//规范2.2.4//保证resolve的调用时机setTimeout(function(){//规范2.2.7.2try{var x = onFulfilled(_self.value);//规范2.2.7.1resolutionProcedure(promise2,x,resolve,reject);}catch(e){reject(e);}},0)})}if(_self.state === REJECTED){return new MyPromise(function(resolve,reject){//规范2.2.4//保证了reject的调用时机setTimeout(function(){//规范2.2.7.2try{var x = onRejected(_self.value);//规范2.2.7.1resolutionProcedure(promise2,x,resolve,reject);}catch(e){reject(e);}})})}
}
复制代码

Promise解决过程的实现

规范上解决过程函数参数严格的限制[[Resolve]](promise2, x)。

function resolutionProcedure(promise2,x,resolve,reject){}
复制代码
避免promise2与x指向同一对象

如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise,避免循环引用。

function resolutionProcedure(promise2,x,resolve,reject){if(promise2 === x){return reject(new TypeError('循环引用'));}
}
复制代码
当x是一个Promise的时候

如果x是一个Promise

  1. 如果x处于等待态,promise需保持等待态直至x被拒绝或执行
  2. 如果x处于执行态,用相同的值执行promise
  3. 如果x处于拒绝态,用相同的据因拒绝promise

在resolutionProcedure中添加代码

if(x instanceof MyPromise){//如果x处于等待太,promise需保持等待态直至x被拒绝或执行if(x.state === PENDING){x.then(function(value){//再次调用该函数是为了确认x resolve的参数是什么类型,如果是基本类型就再次resolve传入下一个thenresolutionProcedure(promise2,value,resolve,reject);},reject)}else{//如果x处于执行态,用相同的值执行promise//如果x处于拒绝态,用相同的据因拒绝promisex.then(resolve,reject);}return;
}
复制代码
当x为对象或者函数的时候

根据规范我们可以得出,当resolve和reject成功执行之后就会忽略掉未执行的一方。在resolutionProcedure中加入一下代码

//规范2.3.3
if(x !== null && (typeof x === 'object' || typeof x === 'function')){//规范2.3.3.2//如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promisetry{//规范2.3.3.1//把 x.then 赋值给 thenvar then =x.then;if(typeof then === 'function'){//规范2.3.3.3//如果 then 是函数,将 x 作为函数的作用域 this 调用之。//传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:then.call(x,//规范2.3.3.3.1//如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)y => {if(called){return ;}called = true;resolutionProcedure(promise2,y,resolve,reject);},//规范2.3.3.3.2//如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promiser =>{if(called){return ;}called = true;resolutionProcedure(promise2,r,resolve,reject);});}else{//规范2.3.3.3.4//如果 then 不是函数,以 x 为参数执行 promiseresolve(x);}}catch(e){//规范2.3.3.3.4if(called){return;}called = true;reject(e);}
}else{//规范2.3.4//如果 x 不为对象或者函数,以 x 为参数执行 promiseresolve(x);
}
复制代码

至此所有PromiseA+规范的东西已经全部实现完了,下面贴出全部加上规范注释的代码。

PromiseA+规范实现

var MyPromise = function(fn){//判断构造函数的参数是否为函数if(typeof fn !== 'function'){throw new TypeError('Promise resolver undefined is not a function');}const _this = this;//promise的状态_this.state = PENDING;//promise的值 _this.value = null;//用于保存then中的回调,当状态为pending的时候才会缓存,并且每个实例至多缓存一次。_this.resolveCallbacks = [];_this.rejectCallbacks = [];//resolve的方法_this.resolve = function(value){//判断参数是不是一个promiseif(value instanceof MyPromise){//如果value是一个promise 递归执行   return value.then(_this.resolve,_this.reject);}//异步执行setTimeout(function(){if(_this.state === PENDING){_this.state = RESOLVED;_this.value = value;_this.resolveCallbacks.forEach(cb => cb(_this.value));}},0)}//reject的方法_this.reject = function(value){//异步执行 保证执行顺序setTimeout(function(){if(_this.state === PENDING){_this.state = REJECTED;_this.value = value;_this.rejectCallbacks.forEach(cb => cb(_this.value));}},0)}//用于解决构造函数传入的函数返回出一个异常的情况//new MyPromise(()=> throw new Error('error'))try{fn(_this.resolve,_this.reject);}catch(e){_this.reject(e);}
}MyPromise.prototype.then = function(onFulfilled,onRejected){const _self = this;//规范2.2.7//promise 的then方法返回一个新的promisevar promise2;//规范2.2.7.3 /2.2.7.4//如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值//如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因//如果参数不是函数就忽略,同时实现透传  new Promise().then().then(x=>x)onResolved = typeof onResolved === 'function' ? onResolved : x => x;onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r};if(_self.state === PENDING){return promise2 = new MyPromise(function(resolve,reject){_self.resolveCallbacks.push(function(){//规范2.2.7.2//如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 etry{var x = onFulfilled(_self.value);//规范2.2.7.1resolutionProcedure(promise2,x,resolve,reject);}catch(e){reject(e);}});_self.rejectCallbacks.push(function(){//规范2.2.7.2try{var x = onRejected(_self.value);//规范2.2.7.1resolutionProcedure(promise2,x,resolve,reject);}catch(e){reject(e);}})})}if(_self.state === RESOLVED){return promise2 = new MyPromise(function(resolve,reject){//规范2.2.4//保证resolve的调用时机setTimeout(function(){//规范2.2.7.2try{var x = onFulfilled(_self.value);//规范2.2.7.1resolutionProcedure(promise2,x,resolve,reject);}catch(e){reject(e);}},0)})}if(_self.state === REJECTED){return promise2 = new MyPromise(function(resolve,reject){//规范2.2.4//保证了reject的调用时机setTimeout(function(){//规范2.2.7.2try{var x = onRejected(_self.value);//规范2.2.7.1resolutionProcedure(promise2,x,resolve,reject);}catch(e){reject(e);}})})}
}
//规范2.3
function resolutionProcedure(promise2,x,resolve,reject){//规范2.3.1//如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise,避免循环引用if(promise2 === x){return reject(new TypeError('循环引用'));}//规范2.3.2//如果x为promiseif(x instanceof MyPromise){//如果x处于等待太,promise需保持等待态直至x被拒绝或执行if(x.state === PENDING){x.then(function(value){//再次调用该函数是为了确认x resolve的参数是什么类型,如果是基本类型就再次resolve传入下一个thenresolutionProcedure(promise2,value,resolve,reject);},reject)}else{//如果x处于执行态,用相同的值执行promise//如果x处于拒绝态,用相同的据因拒绝promisex.then(resolve,reject);}return;}//规范2.3.3.3.3//如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用var called = false;//规范2.3.3if(x !== null && (typeof x === 'object' || typeof x === 'function')){//规范2.3.3.2//如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promisetry{//规范2.3.3.1//把 x.then 赋值给 thenvar then =x.then;if(typeof then === 'function'){//规范2.3.3.3//如果 then 是函数,将 x 作为函数的作用域 this 调用之。//传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:then.call(x,//规范2.3.3.3.1//如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)y => {if(called){return ;}called = true;resolutionProcedure(promise2,y,resolve,reject);},//规范2.3.3.3.2//如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promiser =>{if(called){return ;}called = true;resolutionProcedure(promise2,r,resolve,reject);});}else{//规范2.3.3.3.4//如果 then 不是函数,以 x 为参数执行 promiseresolve(x);}}catch(e){//规范2.3.3.3.4if(called){return;}called = true;reject(e);}}else{//规范2.3.4//如果 x 不为对象或者函数,以 x 为参数执行 promiseresolve(x);}
}
复制代码

相关文章:

黄浴:基于深度学习的超分辨率图像技术发展轨迹一览

作者 | 黄浴转载自知乎导读:近年来,使用深度学习技术的图像超分辨率(SR)取得了显著进步。本文中,奇点汽车自动驾驶首席科学家黄浴对基于深度学习技术的图像超分辨率技术进行了一次全面的总结,分析了这门技术…

Qt简介、安装及在Ubuntu14.04 32位上简单使用举例

Qt是一个跨平台的C图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序。Qt是面向对象的框架,很容易扩展。Qt是一个C工具包,它由几百个C类构成,你在程序中可以使用这些类。Qt具有OOP的所有优点。 跨平台的…

FOSCommentBundle功能包:设置Doctrine ODM映射(投票)

原文出处:12b-mapping_mongodb.md原文作者:FriendsOfSymfony授权许可:创作共用协议翻译人员:FireHare校对人员:适用版本:FOSCommentBundle 2.0.5文章状态:草译阶段Step 12b: Setup MongoDB mapp…

Python最大堆排序实现方法

Python最大堆排序实现方法,具体代码如下: # -*- coding: utf-8 -*- def merge_sort(seq, cmpcmp, sentinelNone): """合并排序,伪码如下: MERGE(A, p, q, r) 1 n1 ← q - p 1 // 前个子序列长度 2 …

内含福利 | 世界人工智能大会:对话大咖,深挖机器学习的商业应用

机器学习作为人工智能时代的关键技术突破,已经在日常生活中广泛应用,给用户带来便利。越来越多的企业也通过机器学习,解决生产和经营中的难题。传统制造业:应用机器学习,部署系统异常检测方案,预测组件寿命…

windows7 64位操作系统上使vs2010和vs2013能够并存的处理方法

之前机子上是只安装有vs2010,后来在没有卸载vs2010的情况下想装个vs2013,使vs2010与vs2013同时并存在windows764位机上。需要依次安装cn_visual_studio_ultimate_2013_x86_dvd_3009109.iso、vs2013.2.iso和vc_mbcsmfc.exe。在安装过程中遇到的问题有: (…

Spring Cloud Alibaba 基础教程:Nacos 生产级版本 0.8.0

Spring Cloud Alibaba 基础教程:Nacos 生产级版本 0.8.0 昨晚Nacos社区发布了第一个生产级版本:0.8.0。由于该版本除了Bug修复之外,还提供了几个生产管理非常重要的特性,所以觉得还是有必要写一篇讲讲这次升级,在后续的…

awk命令使用和取出数据的最大值,最小值和平均值

得到取出数据的最大值:cat manager.txt |grep monitor|awk {print$9}|sort -rn|head -1得到取出数据的最小值:cat manager.txt|grep monitor |awk {print $9}|sort -n|head -1得到取出数据的平均值:cat manager.txt|grep monitor |awk {print…

windows7 64位机上CUDA7.0配置及在VS2010中的简单使用举例

1. 查看本机配置,查看显卡类型是否支持NVIDIA GPU,选中计算机--> 右键属性 --> 设备管理器 --> 显示适配器:NVIDIA GeForce GT 610,从https://developer.nvidia.com/cuda-gpus可以查到相应显卡的compute capabili…

用友云平台,真正的云原生架构,加速云应用落地

数字化经济的出现,企业需要通过新技术实现数字化转型,完成企业管理和业务模式变革。而云计算是数字化中尤为重要且能够更快实现的技术手段。真正的云应用必须是基于云原生架构的,PaaS是一个重要的步骤,因为这是云原生的第一接触点…

从ACM班、百度到亚马逊,深度学习大牛李沐的开挂人生

“大神”,是很多人对李沐的印象。作为一经推出便大受追捧的 MXNet 深度学习框架的主要贡献者之一,李沐功不可没。值得注意的是,这个由 DMLC(Distributed Machine Learning Community)打造的深度学习框架,创…

Linux基础介绍

Linux的创始人Linus Torvalds。Linux的官方标准发音为[linəks]。Linux和Unix是非常像的,Linux就是根据Unix演变过来的。Linux是免费的,其实只是说Linux的内核免费。在Linux内核的基础上产生了众多的Linux版本。Linux的发行版说简单点就是将Linux内核与应…

Go在区块链的发展和演进

Go语言发展至今已经过去十年多了,是目前最流行的新兴语言,云计算领域的首选语言,而且目前随着区块链的流行,Go再次成为了这个领域的第一语言,以太坊,IBM的fabric等重量级的区块链项目都是基于Go开发。 原文…

一天掌握AI核心技术,上手应用,开发者该划哪些重点?

Alpha Go 只会下棋,却并不擅长垃圾分类;智能助手已经可以执行很多任务,但距离真正的人机自然交互还很远。如今 AI 的发展面临着诸多瓶颈,基础理论研究缺失,深度学习的黑箱属性无解,把一切托付于未知并不可靠…

学会这21条,你离Vim大神就不远了

来源 | Python编程时光(ID: Python-Time)导语:作者本人是 Vim 的重度使用者,就因为喜欢上这种双手不离键盘就可以操控一切的feel,Vim 可以让人对文本的操作更加精准、高效。对于未使用过 Vim 的朋友来说,可…

C 语言 和 C++语言的对比学习   二 数据类型

不管是什么语言,我们最习惯的是通过 “hello world” ,来昭告世界,我们有了新的语言来向这个世界问好,尽管真正属于我们自己的其实是哭声。(呵呵,笑点有点低),下面我们来介绍最为基础…

Makefile语法基础介绍

在Linux下,make是一个命令工具,是一个解释Makefile中指令的命令工具。make命令执行时,需要一个Makefile文件,以告诉make命令需要怎么样去编译和链接程序。 make如何工作:在默认的方式下,只输入make命令&am…

MaxCompute studio与权限那些事儿

背景知识 MaxCompute拥有一套强大的安全体系,来保护项目空间里的数据安全。用户在使用MaxCompute时,应理解权限的一些基本概念: 权限可分解为三要素,即主体(用户账号或角色),客体(表…

GitHub标星3w+的项目,全面了解算法和数据结构知识

作者 | 程序员小吴来源 | 五分钟学算法(ID: CXYxiaowu)导语:今天分享一个开源项目,里面汇总了程序员技术面试时需要了解的算法和数据结构知识,并且还提供了相应的代码,目前 GitHub 上标星 35000 star&#…

Shell脚本基础介绍

shell基础简介:编写脚本通常使用某种基于解释器的编程语言。而shell脚本不过就是一些文件,我们能将一系列需要执行的命令写入其中,然后通过shell来执行这些脚本。进入Linux系统(Ubuntu),打开终端Terminal,”$”表示普通…

「小程序JAVA实战」小程序的举报功能开发(68)

转自:https://idig8.com/2018/09/25/xiaochengxujavashizhanxiaochengxudeweixinapicaidancaozuo66-2/ 通过点击举报按钮,跳转到举报页面完成举报操作。 后台开发 获取发布人的userId,videoId,创建者的Id controllerUserControlle…

tar常见文件解压法

2019独角兽企业重金招聘Python工程师标准>>> tar常见文件解压法:.gz - z 小写.bz2 - j 小写.xz - J 大写.Z - Z大写 转载于:https://my.oschina.net/open1900/blog/149238

cookie的作用域

当我们给网站设置cookie时,大家有没有发现在网站的其他域名下也接收到了这些cookie。这些没用的cookie看似不占多少流量,但如果对一个日PV千万的站点来说,那浪费的资源就不是一点点了。因此在设置cookie时,对它的作用域一定要设置…

必看,10篇定义计算机视觉未来的论文

译者 | Major编辑 | 赵雪出品 | AI科技大本营(ID:rgznai100)导语:如果你没能参加 CVPR 2019 , 别担心。本文列出了会上人们最为关注的 10 篇论文,覆盖了 DeepFakes(人脸转换), Facial Recogniti…

有效的rtsp流媒体测试地址汇总

以下是从网上搜集的一些有效的rtsp流媒体测试地址: 1. rtsp://218.204.223.237:554/live/1/0547424F573B085C/gsfp90ef4k0a6iap.sdp 2. rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp 3. rtsp://211.139.194.251:554…

java简单的ID生成器

2019独角兽企业重金招聘Python工程师标准>>> https://www.cnblogs.com/hongdada/p/9324473.html https://github.com/apache/incubator-shardingsphere 转载于:https://my.oschina.net/u/3005325/blog/3006311

安装、设置与启动MySql5.1.30绿色版的方法

1、解压 mysql-noinstall-5.1.30-win32.zip(下载地址http://dev.mysql.com/downloads/mysql/5.1.html)2、在 F 盘建立目录 MySql\MySqlServer5.1\ 3、把解压的内容复制到 F:\MySql\MySqlServer5.1\4、在 F:\MySql\MySqlServer5.1\ 中找 my-large.ini 把它复制成 my.ini5、在…

网页中插入VLC播放器播放rtsp视频流步骤

1. 仿照http://download.csdn.net/detail/haowenxin123456789/8044245 中步骤; 2. 从http://www.videolan.org/vlc/index.html 中下载 vlc-2.2.1-win32.exe 并安装到D:\\ProgramFiles文件夹下; 3. 运行:regsvr32 D:\\ProgramFil…

@程序员,“10倍工程师”都在追这四大AI风向

技术的发展,驱动着产业变革,从而改变着我们的生活方式。当5GAI 时代来临,核心的技术生产力就是开发者:开发者研究前沿的科学创新,推动技术发展,将技术应用于实际场景中。开发者是企业实现商业价值必不可少的…

End Credits

我不知道怎么把他删掉... 今晚WC文艺汇演wwww(等待唱歌.jpg 要是能截到屏一定发上来qwqqqqq 话说这首曲子是新发现的QAQ(Xeuphoria的还是那么好听qwqqq 今天学了快读qvq 还有...dpwww P2015 二叉苹果树 有一棵苹果树,如果树枝有分叉,一定是分2叉&#xf…