聊一聊promise的前世今生

  promise的概念已经出现很久了,浏览器、nodejs都已经全部实现promise了。现在来聊,是不是有点过时了?

  确实,如果不扯淡,这篇随笔根本不会有太多内容。所以,我就尽可能的,多扯一扯,聊一聊promise的另一面。

  大家应该都知道怎么创建一个promise

var promise = new Promise(resolve => {setTimeout(() => resolve('tarol'), 3000) 
});

  如果从业时间长一点,会知道以前的promise不是这么创建的。比如如果你用过jquery,jquery在1.5引入deferred的概念,里面是这样创建promise的

var defer = $.Deferred();
var promise = defer.promise();

  如果你用过angular,里面有个promise service叫$q,它又是这么创建promise的

var defer = $q.defer();
var promise = defer.promise;

  好了,这里已经有三种创建promise的方式了。其中第一种是现在最常见的,第二种和第三种看上去很像,但却有细微的差别。比如jquery里面是通过执行函数promise()返回promise,而angular中defer的属性就是promise。如果你还有兴趣,那么我从头开始讲。

  promise的引入是为了规范化异步操作,随着前端的逻辑越来越复杂,异步操作的问题越来越亟待解决。首先大量的异步操作形成了N级的大括号,俗称“回调地狱”;其次callback的写法没有标准,nodejs里面的callback一般是(err, data) => {...},jquery里面的success callback又是data => {...}。在这种场景下,很多异步流程控制的类库应运而生。

  作为前端,一般最早接触promise的概念是在jquery的1.5版本发布的deferred objects。但是前端最早引入promise的概念的却不是jquery,而是dojo,而且promise之所以叫promise也是因为dojo。Promises/A标准的撰写者KrisZyp于09年在google的CommonJS讨论组发了一个贴子,讨论了promise API的设计思路。他声称想将这类API命名为future,但是dojo已经实现的deferred机制中用到了promise这个术语,所以还是继续使用promise为此机制命名。之后便有了CommonJS社区的这个proposal,即Promises/A。如果你对什么是deferred,什么是promise还存在疑问,不要急,先跳过,后面会讲到。

  Promises/A是一个非常简单的proposal,它只阐述了promise的基本运行规则

  1. promise对象存在三种状态:unfulfilled, fulfilled和failed
  2. 一旦promise由unfulfilled切换为fulfilled或者failed状态,它的状态不可再改变
  3. proposal没有定义如何创建promise
  4. promise对象必须包含then方法:then(fulfilledHandler, errorHandler, progressHandler)
  5. 交互式promise对象作为promise对象的扩展,需要包含get方法和call方法:get(propertyName)、call(functionName, arg1, arg2, ...)

  如果你研究过现在浏览器或nodejs的promise,你会发现Promises/A好像处处相似,但又处处不同。比如三种状态是这个叫法吗?progressHandler没见过啊!get、call又是什么鬼?前面两个问题可以先放一放,因为后面会做出解答。第三个问题这里解释下,什么是get,什么是call,它们的设计初衷是什么,应用场景是什么?虽然现在你轻易见不到它们了,但是了解它们有助于理解后面的部分内容。

  一般来说,promise调用链存在两条管道,一条是promise链,就是下图一中的多个promise,一条是回调函数中的值链,就是下图二中的多个value或reason

  

  现在我们都知道,值链中前一个callback(callback1)的返回值是后一个callback(callback2)的入参(这里仅讨论简单值类型的fulfilled的情况)。但是如果我callback1返回的是a,而callback2的入参我希望是a.b呢?或许你可以说那我callback1返回a.b就是了,那如果callback1和callback2都是固定的业务算法,它们的入参和返回都是固定的,不能随便修改,那又怎么办呢?如果promise只支持then,那么我们需要在两个then之间插入一个新的then:promise.then(callback1).then(a => a.b).then(callback2)。而get解决的就是这个问题,有了get后,可以这么写:promise.then(callback1).get('b').then(callback2),这样promise链条中就可以减少一些奇怪的东西。同理,当a.b是一个函数,而callback2期望的入参是a.b(c),那么可以这样写:promise.then(callback1).call('b', c).then(callback2)。

  我们回到之前的话题,现在常见的promise和Promise/A到底是什么关系,为什么会有花非花雾非雾的感觉?原因很简单,常见的promise是参照Promises/A的进阶版——Promises/A+定义的。

  Promises/A存在一些很明显的问题,如果你了解TC39 process或者RFC等标准审核流程,你会发现:

  1. 首先Promise/A里面用语不规范,尤其是对术语的使用
  2. 只描述API的用途,没有详细的算法

  Promises/A+就是基于这样的问题产生的,要说明的是Promises/A+的维护者不再是前面提到的KrisZyp,而是由一个组织维护的。

  组织的成员如下,其中圈出来的另一个Kris需要留意一下,之后还会提到他。

  Promises/A+在Promises/A的基础上做了如下几点修正:

  1. 移除了then的第三个入参progressHandler,所以你见不到了
  2. 移除了交互式promise的API:get和call,所以你用不了了
  3. 规定promise2 = promise1.then(...)中允许promise1 === promise2,但是文档必须对此情况进行说明
  4. promise的三种状态术语化:pending,fulfilled,rejected
  5. 规定fulfilled传递的参数叫value,rejected传递的参数叫reason
  6. 严格区分thenable和promise,thenable作为promise的鸭子类型存在,thenable是什么、鸭子类型是什么,下面会解释
  7. 使用正式且标准的语言描述了then方法的逻辑算法,promises-aplus还提供了验证实现的test case

  Promises/A+没有新增任何API,而且删掉了Promises/A的部分冗余设计。这样一来,Promises/A+其实只规定了,promise对象必须包含指定算法的方法then。接下来我会归整下所谓的then算法,以及它存在哪些不常见的调用方式。

then的基本调用方式:promise.then(onFulfilled, onRejected),我默认你已经掌握了基础的then调用,所以常见的场景以下不做举例。
  1. onFulfilled和onRejected都是可选的,如果省略了或者类型不是函数,前面流过来的value或者reason直接流到下一个callback,我们举两个极端的例子
    Promise.resolve('resolve').then().then(value => console.log(value))    // resolve
    Promise.reject('reject').then().then(void 0, reason => console.log(reason))    //reason
    

    这个特性决定了我们现在可以这样写异常处理

    Promise.reject('reason').then(v => v).then(v => v).then(v => v).catch(reason => console.log(reason))    //reason
    

    但是如果你在then链条中,插入一个空的onRejected,reason就流不到catch了。因为onRejected返回了undefined,下一个promise处于fulfilled态

    Promise.reject('reason').then(v => v).then(v => v).then(v => v, () => {}).catch(reason => console.log(reason))
    

      

  2. onFulfilled或onRejected只能调用一次,且只能以函数的形式被调用,对应的是不能以属性方法的方式被调用,比如
    var name = 'tarol';
    var person = {name: 'okal',say: function() {console.log(this.name);}
    }
    person.say(); //okal
    Promise.resolve('value').then(person.say);  //tarol
    

    如果你想第二行还是打印出'okal',请使用bind

    Promise.resolve('value').then(person.say.bind(person));  //okal
    

      

  3. var promise2 = promise1.then(onFulfilled, onRejected)
    

    onFulfilled或者onRejected中抛出异常,则promise2状态置为rejected

  4. 上面的例子中,onFulfilled或者onRejected如果返回了任意值x(如果不存在return语句,则是返回undefined),则进入解析过程[[Resolve]](promise2, x)

    解析过程[[Resolve]](promise2, x)算法如下
    1. 如果x是promise,则promise2的状态取决于x的状态
    2. 那么你会想,如果x === promise2呢?promise2的状态取决于本身的状态?这就像把obj的原型设置为自身一样肯定是不允许的。所以其实在第一条规则之前,还有一条:如果x === promise2,抛出TypeError。之所以把这条规则放到下面,是用前一条规则引出这条规则的必要性
    3. 如果x不是对象,promise2置为fulfilled,value为x
    4. 如果x是对象
      1. 访问x.then时,如果抛出异常,则promise2置为rejected,reason为抛出的异常
        var obj = {get then() {throw 'err'}};
        Promise.resolve('value').then(v => obj).catch(reason => console.log(reason));    // err
        

          

      2. 如果then不是函数,则同3
        Promise.resolve('value').then(v => {return {name: 'tarol',then: void 0}
        }).then(v => console.log(v.name));  //tarol
        

          

        如果then是函数,那么x就是一个thenable,then会被立即调用,传入参数resolve和reject,并绑定x作为this。
        1. 如果执行过程中调用了resolve(y),那么进入下一个解析过程[[Resolve]](promise2, y),可以看出解析过程实际上是一个递归函数
        2. 如果调用了reject(r),那么promise2置为rejected,reason为r
        3. 调用resolve或reject后,后面的代码依然会运行
          Promise.resolve('value').then(v => {return {then: (resolve, reject) => {resolve(v);console.log('continue');  //  continue}}
          }).then(v => console.log(v)); //  value
          

            

        4. 如果既调用了resolve、又调用了reject,仅第一个调用有效
          Promise.resolve('value').then(v => {return {then: (resolve, reject) => {resolve('resolve');reject('reject')}}
          }).then(v => console.log(v), r => console.log(r)); //  resolve
          

            

        5. 如果抛出了异常,而抛出的时机在resolve或reject前,promise2置为rejected,reason为异常本身。如果抛出的时机在resolve或reject之后,则忽略这个异常。以下case在chrome 66上运行失败,promise处于pending状态不切换,但是在nodejs v8.11.1上运行成功
          Promise.resolve('value').then(v => {return {then: (resolve, reject) => {resolve('resolve');throw 'err';}}
          }).then(v => console.log(v), r => console.log(r)); //  resolve
          

            

          Promise.resolve('value').then(v => {return {then: (resolve, reject) => {throw 'err';resolve('resolve');}}
          }).then(v => console.log(v), r => console.log(r)); //  err

  上面的例子中涉及到一个重要的概念,就是thenable。简单的说,thenable是promise的鸭子类型。什么是鸭子类型?搜索引擎可以告诉你更详尽的解释,长话短说就是“行为像鸭子那么它就是鸭子”,即类型的判断取决于对象的行为(对象暴露的方法)。放到promise中就是,一个对象如果存在then方法,那么它就是thenable对象,可以作为特殊类型(promise和thenable)进入promise的值链。

  promise和thenble如此相像,但是为什么在解析过程[[Resolve]](promise2, x)中交由不同的分支处理?那是因为虽然promise和thenable开放的接口一样,但过程角色不一样。promise中then的实现是由Promises/A+规定的(见then算法),入参onFulfilled和onRejected是由开发者实现的。而thenable中then是由开发者实现的,入参resolve和reject的实现是由Promises/A+规定的(见then算法3.3.3)。thenable的提出其实是为了可扩展性,其他的类库只要实现了符合Promises/A+规定的thenable,都可以无缝衔接到Promises/A+的实现库中。

  Promises/A+先介绍到这里了。如果你细心,你会发现前面漏掉了一个关键的内容,就是之前反复提到的如何创建promise。Promise/A+中并没有提及,而在当下来说,new Promise(resolver)的创建方式仿佛再正常不过了,普及程度让人忘了还有deferred.promise这种方式。那么Promise构造器又是谁提出来的,它为什么击败了deferred成为了promise的主流创建方式?

  首先提出Promise构造器的标准大名鼎鼎,就是es6。现在你见到的promise,一般都是es6的实现。es6不仅规定了Promise构造函数,还规定了Promise.all、Promise.race、Promise.reject、Promise.resolve、Promise.prototype.catch、Promise.prototype.then一系列耳熟能详的API(Promise.try、Promise.prototype.finally尚未正式成为es标准),其中then的算法就是将Promises/A+的算法使用es的标准写法规范了下来,即将Promises/A+的逻辑算法转化为了es中基于解释器API的具体算法。

  那么为什么es6放弃了大行其道的deferred,最终敲定了Promise构造器的创建方式呢?我们写两个demo感受下不同

var Q = require("q");var deferred = Q.defer();deferred.promise.then(v => console.log(v));setTimeout(() => deferred.resolve("tarol"), 3000);

  

var p = new Promise(resolve => {setTimeout(() => resolve("tarol"), 3000);
});p.then(v => console.log(v));

  前者是deferred方式,需要依赖类库Q;后者是es6方式,可以在nodejs环境直接运行。

  如果你习惯使用deferred,你会觉得es6的方式非常不合理:

  首先,promise的产生的原因之一是为了解决回调地狱的问题,而Promise构造器的方式在构造函数中直接注入了一个函数,如果这个函数在复杂点,同样存在一堆大括号。

  其次,promise基于订阅发布模式实现,deferred.resolve/reject可以理解为发布器/触发器(trigger),deferred.promise.then可以理解为订阅器(on)。在多模块编程时,我可以在一个公共模块创建deferred,然后在A模块引用公共模块的触发器触发状态的切换,在B模块引用公共模块使用订阅器添加监听者,这样很方便的实现了两个没有联系的模块间互相通信。而es6的方式,触发器在promise构造时就生成了并且立即进入触发阶段(即创建promise到promise被fulfill或者reject之间的过程),自由度减少了很多。

  我一度很反感这种创建方式,认为这是一种束缚,直到我看到了bluebird(Promise/A+的实现库)讨论组中某个帖子的解释。大概说一下,回帖人的意思是,promise首先应该是一个异步流程控制的解决方案,流程控制包括了正常的数据流和异常流程处理。而deferred的方式存在一个致命的缺陷,就是promise链的第一个promise(deferred.promise)的触发阶段抛出的异常是不交由promise自动处理的。我写几个demo解释下这句话

var Q = require("q");var deferred = Q.defer();deferred.promise.then(v => {throw 'err'
}).catch(reason => console.log(reason));  // errsetTimeout(() => deferred.resolve("tarol"));

  以上是一个正常的异常流程处理,在值链中抛出了异常,自动触发下一个promise的onRejected。但是如果在deferred.promise触发阶段的业务流程中抛出了异常呢?

var Q = require("q");var deferred = Q.defer();deferred.promise.catch(reason => console.log(reason));  // 不触发setTimeout(() => {throw "err";deferred.resolve("tarol");
});

  这个异常将抛出到最外层,而不是由promise进行流程控制,如果想让promise处理抛出的异常,必须这么写

var Q = require("q");var deferred = Q.defer();deferred.promise.catch(reason => console.log(reason));  // errsetTimeout(() => {try {throw "err";} catch (e) {deferred.reject(e);}
});

  deferred的问题就在这里了,在deferred.promise触发阶段抛出的异常,不会自动交由promise链进行控制。而es6的方式就简单了

var p = new Promise(() => {throw "err";
});p.catch(r => console.log(r));  // err

  可见,TC39在设计Promise接口时,首先考虑的是将Promise看作一个异步流程控制的工具,而非一个订阅发布的事件模块,所以最终定下了new Promise(resolver)这样一种创建方式。

  但是如果你说:我不听,我不听,deferred就是比new Promise好,而且我的promise在触发阶段是不会抛出异常的。那好,还有另外一套标准满足你,那就是Promises/B和Promises/D。其中Promises/D可以看做Promises/B的升级版,就如同Promises/A+之于Promises/A。这两个标准的撰写者都是同一个人,就是上面Promises/A+组织中圈起来的大胡子,他不仅维护了这两个标准,还写了一个实现库,就是上面提到的Q,同时angular中的$q也是参照Q实现的。

  Promises/B和Promises/D(以下统称为Promises/B)都位于CommonJS社区,但是由于没有被社区采用,处于废弃的状态。而Q却是一个长期维护的类库,所以Q的实现和两个标准已经有所脱离,请知悉。

  Promises/B和es6可以说是Promises/A+的两个分支,基于不同的设计理念在Promises/A+的基础上设计了两套不同的promise规则。鉴于Promises/A+在创建promise上的空白,Promises/B同样提供了创建promise的方法,而且是大量创建promise的方法。以下这些方法都由实现Promises/B的模块提供,而不是Promises/B中promise对象的方法。

  1. when(value, callback, errback_opt):类似于es6中Promise.resolve(value).then(callback, errback_opt)
  2. asap(value, callback, errback_opt):基本逻辑同when,但是when中callback的调用会放在setTimeout(callback, 0)中,而asap中callback是直接调用,该接口在Q中已经废弃
  3. enqueue(task Function):将一个callback插入队列并执行,其实就是fn => setTimeout(fn, 0),该接口在Q中已经废弃
  4. get(object, name):类似于Promise.resolve(object[name])
  5. post(object, name, args):类似于Promise.resolve(object[name].apply(object, args))
  6. put(object, name, value):类似于Promise.resolve({then: resolve => object[name] = value; resolve()}),该接口在Q中重命名为set
  7. del(object, name):类似于Promise.resolve({then: resolve => delete object[name]; resolve()}),该接口在Q中alias为delete
  8. makePromise:创建一个流程控制类的promise,并自定义其verbs方法,verbs方法指以上的get、post、put、del
  9. defer:创建一个deferred,包含一个延时类的promise
  10. reject:创建一个rejected的流程控制类promise
  11. ref:创建一个resolve的流程控制类promise,该接口在Q中重命名为fulfill
  12. isPromise:判断一个对象是否是promise
  13. method:传入verbs返回对应的函数,如method('get')即是上面4中的get,已废弃

  不知道以上API的应用场景和具体用法不要紧,我们先总结一下。Promises/B和es6理念上最大的出入在于,es6更多的把promise定义为一个异步流程控制的模块,而Promises/B更多的把promise作为一个流程控制的模块。所以Promises/B在创建一个promise的时候,可以选择使用makePromise创建一个纯粹的操作数据的流程控制的promise,而get、post、put、del、reject、ref等都是通过调用makePromise实现的,是makePromise的上层API;也可以使用defer创建一个deferred,包含promise这个属性,对应一个延时类的promise。

  延时类的promise经过前面的解释基本都了解用法和场景,那对数据进行流程控制的promise呢?在上面Promises/A部分说明了get和call两个API的用法和场景,Promises/B的get对应的就是Promises/A的get,call对应的是post。put/set是Promises/B新增的,和前二者一样,在操作数据时进行流程控制。比如在严格模式下,如果对象a的属性b的writable是false。这时对a.b赋值,是会抛出异常的,如果异常未被捕获,那么会影响后续代码的运行。

"use strict";
var a = {};Object.defineProperty(a, "name", {value: "tarol",writable: false
});a.name = "okay";console.log("end");  // 不运行

  这时候如果使用Q的put进行流程控制,就可以把赋值这部分独立开来,不影响后续代码的运行。

"use strict";
var Q = require("q");var a = {};Object.defineProperty(a, "name", {value: "tarol",writable: false
});Q.set(a, "name", "okay").then(() => console.log("success"),() => console.log("fail")  // fail
);console.log("end");  // end

  这部分的应用场景是否有价值呢?答案就是见仁见智了,好在Q还提供了makePromise这个底层API,自定义promise可以实现比增删改查这些verbs更强大的功能。比如当我做数据校验的时候可以这样写

var Q = require("q");var p = Q.makePromise({isNumber: function(v) {if (isNaN(v)) {throw new Error(`${v} is not a number`);} else {return v;}}
});p.dispatch("isNumber", ["1a"]).then(v => console.log(`number is ${v}`)).catch(err => console.log("err", err));  // 1a is not a number
p.dispatch("isNumber", ["1"]).then(v => console.log(`number is ${v}`))  // number is 1.catch(err => console.log("err", err));

  以上不涉及任何异步操作,只是用Q对某个业务功能做流程梳理而已。

  而且Q并未和es6分家,而是在后续的版本中兼容了es6的规范(Q.Promise对应es6中的全局Promise),成为了es6的父集,加之Q也兼容了Promises/A中被A+抛弃的部分,如progressHandler、get、call(post)。所以对于Q,你可以理解为promise规范的集大成者,整体来说是值得一用的。

  最后要提到的是最为式微的promise规范——Promises/KISS,它的实现库直接用futures命名,实现了KrisZyp未竟的心愿。如果比较github上的star,KISS甚至不如我没有提及的then.js和when。但是鉴于和Q一样,是有一定实践经验后CommonJS社区promise规范的提案,所以花少量的篇幅介绍一下。

  Promises/KISS不将Promises/A作为子集,所以它没有提供then作为订阅器,代之的是when和whenever两个订阅器。触发器也不是常见的resolve、reject,而是callback、errback和fulfill。其中callback类似于notify,即progressHandler的触发器,errback类似于reject,fulfill类似于resolve。

  为什么会有两个订阅器呢?因为KISS不像Promises/A,A中的then中是传入三个监听器,其中progressHandler还可以多次触发。但是KISS中的when和whenever一次只能传入一个监听器,所以它要解决的是,同一种订阅方式,怎么订阅三种不同的监听器?

  首先,怎么区分fulfilledHandler和errorHandler呢?KISS借鉴了nodejs的回调函数方式,第一个参数是err,第二个参数是data。所以fulfilledHandler和errorHandler在一个监听器里这样进行区分:

function(err, data) {if (err) {...}    // errorHandlerelse {...}    // fulfilledHandler
}

  那怎么区分多次调用的progressHandler呢?使用when注册的监听器只能调用一次,使用whenever注册的监听器可以调用多次。我们写个demo区分Q和KISS的API的不同:

var Q = require("q");
var defer = Q.defer();
defer.promise.then(v => console.log("fulfill", v),err => console.log("reject", err),progress => console.log("progress", progress)
);
defer.notify(20);  // progress 20
defer.notify(30);  // progress 30
defer.notify(50);  // progress 50
defer.resolve("ok");  // fulfill ok

  

var future = require("future");var p = new future();
var progressHandler = function(err, progress) {if (err) {console.log("err", err);} else {console.log("progress", progress);}
};
p.whenever(progressHandler);
p.callback(20);  // progress 20
p.callback(30);  // progress 30
p.callback(50);  // progress 50
p.removeCallback(progressHandler);  // 需要移除监听器,不然fulfill时也会触发
p.when(function(err, v) {   // 需要在callback调用后注册fulfill的监听器,不然callback会触发if (err) {console.log("reject", err);} else {console.log("fulfill", v);}
});
p.fulfill(void 0, "ok");  // fulfill ok

  可见,实现同样的需求,使用future会更麻烦,而且还存在先后顺序的陷阱(我一向认为简单类库的应用代码如果存在严重的先后顺序,是设计的不合格),习惯使用es6的promise的童鞋还是不建议使用KISS标准的future。

  整篇文章就到这里,前面提到的then.js和when不再花篇幅介绍了。因为promise的实现大同小异,都是订阅发布+特定的流程控制,只是各个标准的出发点和侧重点不同,导致一些语法和接口的不同。而随着es标准的越来越完善,其他promise的标准要么慢慢消亡(如future、then.js),要么给后续的es标准铺路(如bluebird、Q)。所以如果你没有什么执念的话,乖乖的跟随es标准是最省事的做法。而这边随笔的目的,一是借机整理一下自己使用各个promise库时长期存在的疑惑;二是告诉自己,很多现在看来尘埃落地的技术并非天生如此,沿着前路走过来会比站在终点看到更精彩的世界。

转载于:https://www.cnblogs.com/tarol/p/9042407.html

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

chromebook刷机_如何在Chromebook上切换(或离开)Canary频道

chromebook刷机Just like Chrome, Google offers multiple channels of the Chrome OS operating system. In addition to the standard Stable, Beta, and Developer channels you can choose from on the About page, there’s a special bleeding-edge Canary channel. The …

C++--day05

目录: 1. C的提高 1-131P 时间七天 2. C的基础 132-286P 时间八天 3. C的提高 287-378P 时间五天 4. C/C的数据结构 379-482P 时间五天 5. C/C的设计模式基础 483-540P 时间三天 视频资料:https://www.bilibili.com/video/av27904891?fromsearch&seid108915144…

libreoffice_如何更改您在LibreOffice中可以撤消的操作数

libreofficeIn LibreOffice programs, you can undo one action after another…to a point. The default number of actions you can undo is 100, but that number is easy to change. 在LibreOffice程序中,您可以撤消一个动作,直到某个点。 您可以撤消…

远程连接服务器出现身份验证错误 要求的函数不受支持

来源:https://www.cnblogs.com/lindajia/p/9021082.html 以往发布程序到服务器都没问题。今天远程桌面连接到服务器,突然出现了异常!异常信息为: 在网上看到有多种解决方案:发现有种修改注册表的方式很简单。 详细步骤…

CDH集群安装配置(五)- Cloudera Manager Server

在线安装 sudo yum install cloudera-manager-daemons cloudera-manager-server 离线安装 资源下载地址 https://archive.cloudera.com/cm6/6.1.0/redhat7/yum/RPMS/x86_64/ 上次下面资源包到cdh1节点 cloudera-manager-server-6.1.0-769885.el7.x86_64.rpm cloudera-manager-s…

c++简单程序设计-5

编程实验部分1.vector3.cpp #include <iostream> #include <vector> #include <string> using namespace std;// 函数声明 void output1(vector<string> &); void output2(vector<string> &); int main() {vector<string>like…

关于JavaScript的编译原理

引擎&#xff1a;负责整个js程序的编译和执行过程编译器&#xff1a;负责语法分析和代码生成作用域&#xff1a;收集和维护一系列查询&#xff08;由所有声明的标识符组成&#xff09; 【例子&#xff1a;声明一个变量并赋值 var a value&#xff1b;】 Step1.编译器对该程序段…

safari检查元素_如何防止Safari检查是否使用Apple Pay

safari检查元素Apple Pay’s incorporation into macOS Sierra makes it really easy to pay using the service on your Mac with your iPhone or iPad. But that doesn’t mean just because you can, you will, or will want to use Apple Pay in the future. 通过将Apple P…

某乎有人问--微软会抛弃C#吗,有点担心?

在某乎有人问&#xff1a;微软会抛弃C#吗&#xff0c;有点担心&#xff1f;&#xff0c;类似这样的问题&#xff0c;一直都有很多人在问&#xff0c;今天我们就来聊聊这个问题。没必要担心微软倒闭了&#xff0c;C#都不会消失&#xff0c;其实.Net已经不属于微软的了。C#是属于…

icloud上传错误_如何修复HomeKit“地址未注册到iCloud”错误

icloud上传错误While Apple has made serious improvements to the HomeKit smarthome framework, there are still more than a few ghosts in the machine. Let’s look at how to banish the extremely frustrating “Address is not registered with iCloud” error to get…

(3)Python3笔记之变量与运算符

一、变量 1&#xff09;. 命名规则&#xff1a; 1. 变量名不能使用系统关键字或保留关键字 2. 变量区分大小写 3. 变量命名由字母&#xff0c;数字&#xff0c;下划线组成但不能以数字开头 4. 不需要声明变量类型 是 a 1 非 int a 1 5. 查看变量内存地址 id(a), id(b) 6…

WPF 实现视频会议与会人员动态布局

WPF 实现视频会议与会人员动态布局控件名&#xff1a;SixGridView作 者&#xff1a;WPFDevelopersOrg - 驚鏵原文链接[1]&#xff1a;https://github.com/WPFDevelopersOrg/WPFDevelopers框架使用.NET40&#xff1b;Visual Studio 2019;接着上一篇是基于Grid实现的视频查看感…

chromebook刷机_如何获取Android应用以查看Chromebook上的外部存储

chromebook刷机Android apps are a great way to expand the sometimes limited capabilities of Chromebooks, but they can be a problem if you store most of your data on an external medium—like an SD card, for example. Android应用程序是扩展Chromebook有时有限功能…

android 指纹添加_如何将手势添加到Android手机的指纹扫描仪

android 指纹添加So you have a shiny new Android phone, equipped with a security-friendly fingerprint scanner. Congratulations! But did you know that, while useful on its own, you can actually make the fingerprint scanner do more than just unlock your phone…

百度高管:问心无愧

1月23日下午消息&#xff0c;今天下午&#xff0c;百度召开百家号2019内容创作者盛典&#xff0c;百度副总裁沈抖出席并发布演讲。 就在前一天&#xff0c;一篇名为《搜索引擎百度已死》的文章刷屏&#xff0c;文中提到百度搜索有一半以上会指向百度自家产品&#xff0c;尤其百…

Vuex 学习笔记

Vuex 是什么&#xff1f; Vuex 是一个专为 Vue.js应用程序开发的状态管理模式。由于SPA应用的模块化&#xff0c;每个组件都有它各自的数据&#xff08;state&#xff09;、视图&#xff08;view&#xff09;和方法&#xff08;actions&#xff09;&#xff0c;当项目内容越来越…

xdf文档怎么转换为pdf_如何将PDF文件和图像转换为Google文档文档

xdf文档怎么转换为pdfYou probably know you can create and edit documents with Google Docs, but you can edit more than just .doc files. Google Drive can also convert any PDF, JPG, PNG, or GIF into a document with fully editable text. Here’s how. 您可能知道可…

在现代 Windows 上使用经典 Windows 2000、XP、Vista 任务栏

你好&#xff0c;这里是 Dotnet 工具箱&#xff0c;定期分享 Dotnet 有趣&#xff0c;实用的工具和组件&#xff0c;希望对您有用&#xff01;前言您第一次使用的 Windows 是哪个版本的&#xff1f;我最早使用的 Windows XP&#xff0c;然后再经过 XP、7、8/8.1 、Windows 10&a…

airdroid黑屏_如何使用AirDroid从PC控制Android设备

airdroid黑屏AirDroid for Android replaces your USB cable for connecting to your PC. Transfer files back and forth, send text messages, play music, view your photos, and manage applications using a web browser or a desktop client. 适用于Android的AirDroid取代…