聊一聊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.pswp.cn/news/280274.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/280274.shtml
英文地址,請注明出處:http://en.pswp.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…

通過 CancellationToken 提高 Web 性能

在 Web 開發中,經常會遇到這樣的場景:用戶發起一個請求,Web 服務器執行一些計算密集型的操作,等待結果返回給用戶。這種情況下,如果用戶在等待結果的過程中取消了請求,那么服務器端依然會繼續執行計算&…

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 以往發布程序到服務器都沒問題。今天遠程桌面連接到服務器,突然出現了異常!異常信息為: 在網上看到有多種解決方案:發現有種修改注冊表的方式很簡單。 詳細步驟…

截取圖片生成頭像插件

上傳頭像插件 目的: 幫助開發者快速開發上傳頭像功能點 背景: 現在b,g能搜到的頭像上傳插件并不太好用,所以想提供一個比較自由度的上傳并且可以剪切的插件。 資源: 具體資源請查看這里 實現大致思路如下: …

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…

spring boot中servlet啟動原理

啟動過程及原理 1 spring boot 應用啟動運行run方法 StopWatch stopWatch new StopWatch();stopWatch.start();ConfigurableApplicationContext context null;FailureAnalyzers analyzers null;configureHeadlessProperty();SpringApplicationRunListeners listeners getRu…

某乎有人問--微軟會拋棄C#嗎,有點擔心?

在某乎有人問&#xff1a;微軟會拋棄C#嗎&#xff0c;有點擔心&#xff1f;&#xff0c;類似這樣的問題&#xff0c;一直都有很多人在問&#xff0c;今天我們就來聊聊這個問題。沒必要擔心微軟倒閉了&#xff0c;C#都不會消失&#xff0c;其實.Net已經不屬于微軟的了。C#是屬于…

mailing list的原理

1 發往mailing list郵箱的郵件會被所有訂閱了該郵箱的人收到 說白了&#xff0c;就是一種郵件群發機制&#xff0c;為了簡化群發&#xff0c;不是將所有的收件人放到收件人列表中&#xff0c;而是發往總的郵箱即可。 2 要向該mailing list郵箱中發送郵件需要先要訂閱 但是&…

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…

Jenkins安裝部署

Jenkins安裝部署 Jenkins簡介 Jenkins是一個開源軟件項目&#xff0c;是基于Java開發的一種持續集成工具&#xff0c;用于監控持續重復的工作&#xff0c;旨在提供一個開放易用的軟件平臺&#xff0c;使軟件的持續集成變成可能。 安裝步驟 本文以CentOS7為環境&#xff0c;安裝…

Angular2中的路由(簡單總結)

Angular2中建立路由的4個步驟&#xff1a; 1、路由配置&#xff1a;最好新建一個app.toutes.ts文件&#xff08;能不能用ng命令新建有待調查&#xff09; Angular2中路由要解決的是URL與頁面的對應關系&#xff08;比如URL是http://localhost:4200/all-people&#xff0c;那么頁…

受 SQLite 多年青睞,C 語言到底好在哪兒?

SQLite 近日發表了一篇博文&#xff0c;解釋了為什么多年來 SQLite 一直堅持用 C 語言來實現&#xff0c;以下是正文內容&#xff1a; C 語言是最佳選擇 從2000年5月29日發布至今&#xff0c;SQLite 一直都是用 C 語言實現。C 一直是實現像 SQLite 這類軟件庫的最佳語言。目前&…

為什么 Random.Shared 是線程安全的

在多線程環境中使用 Random 類來生成偽隨機數時&#xff0c;很容易出現線程安全問題。例如&#xff0c;當多個線程同時調用 Next 方法時&#xff0c;可能會出現種子被意外修改的情況&#xff0c;導致生成的偽隨機數不符合預期。為了避免這種情況&#xff0c;.NET 框架引入了 Ra…

(3)Python3筆記之變量與運算符

一、變量 1&#xff09;. 命名規則&#xff1a; 1. 變量名不能使用系統關鍵字或保留關鍵字 2. 變量區分大小寫 3. 變量命名由字母&#xff0c;數字&#xff0c;下劃線組成但不能以數字開頭 4. 不需要聲明變量類型 是 a 1 非 int a 1 5. 查看變量內存地址 id(a), id(b) 6…