搜尋
首頁web前端js教程JavaScript程式設計中的Promise使用大全_基礎知識

儘管Promise已經有自己的規範,但目前的各類Promise函式庫,在Promise的實作細節上是有差異的,部分API甚至在意義上完全不同。但Promise的核心內容,是相通的,它就是then方法。在相關術語中,promise指的就是一個有then方法,且該方法能觸發特定行為的物件或函數。

Promise可以有不同的實作方式,因此Promise核心說明並不會討論任何具體的實作程式碼。

先閱讀Promise核心說明的意思是:看,這就是需要寫出來的結果,請參考這個結果想一想怎麼用程式碼寫出來吧。

起步:用這種方式理解Promise

回想一下Promise解決的是什麼問題?回調。例如,函數doMission1()代表第一件事情,現在,我們想要在這件事情完成後,再做下一件事情doMission2(),應該怎麼做呢?

先看看我們常見的回調模式。 doMission1()說:「你要這麼做的話,就把doMission2()交給我,我結束後幫你呼叫。」所以會是:

複製程式碼 程式碼如下:
doMission1(doMission2);
doMission1(doMission2);

doMission1(doMission2);

Promise模式又是如何呢?你對doMission1()說:「不行,控制權要在我這裡。你應該改變一下,你先返回一個特別的東西給我,然後我來用這個東西安排下一件事。」這個特別的東西就是Promise,這會變成這樣:
複製程式碼
程式碼如下:

doMission1().then(doMission2);可以看出,Promise將回呼模式的主從關係調換了一個位置(翻身做主人!),多個事件的流程關係,就可以這樣集中到主幹道上(而不是分散在各個事件函數之內)。

好了,如何做這樣一個轉換呢?從最簡單的情況來吧,假設doMission1()的程式碼是:

複製程式碼
程式碼如下:

function doMission1(callback){ 
  var value = 1; 
  callback(value);  }

那麼,它可以改一下,變成這樣:

複製程式碼
程式碼如下:

function doMission1(){ 

function doMission1(){ 
  return { 
    then: function(callback){ 
      var value = 1; 
      callback(value);      } 

  }; 

}

  這就完成了轉換。雖然並不是實際有用的轉換,但到這裡,其實已經觸及了Promise最為重要的實作要點,即Promise將回傳值轉換為帶有then方法的物件。

進階:Q的設計路程

從defer開始
var defer = function () { 
 var pending = [], value; 
 return { 
 resolve: function (_value) { 
  value = _value; 
  for (var i = 0, ii = pending.length; i < ii; i++) { 
  var callback = pending[i]; 
  callback(value); 
  } 
  pending = undefined; 
 }, 
 then: function (callback) { 
  if (pending) { 
  pending.push(callback); 
  } else { 
  callback(value); 
  } 
 } 
 } 
}; 
design/q0.js是Q初步成型的第一步。它創建了一個名為defer的工具函數,用於建立Promise:

這段原始碼可以看出,執行defer()將會得到一個對象,該物件包含resolve和then兩個方法。請回想一下jQuery的Deferred(同樣有resolve和then),這兩個方法將會是近似的效果。 then會參考pending的狀態,如果是等待狀態則會回呼儲存(push),否則立即呼叫回呼。 resolve則會肯定這個Promise,更新值的同時運行完所有已儲存的回呼。 defer的使用範例如下:
複製程式碼


代碼如下:

var oneOneSecondLater = function () {    var result = defer(); 
  setTimeout(function () { 
    result.resolve(1); 
  }, 1000);    return result;  };


oneOneSecondLater().then(callback);

这里oneOneSecondLater()包含异步内容(setTimeout),但这里让它立即返回了一个defer()生成的对象,然后将对象的resolve方法放在异步结束的位置调用(并附带上值,或者说结果)。

到此,以上代码存在一个问题:resolve可以被执行多次。因此,resolve中应该加入对状态的判断,保证resolve只有一次有效。这就是Q下一步的design/q1.js(仅差异部分):

resolve: function (_value) { 
 if (pending) { 
 value = _value; 
 for (var i = 0, ii = pending.length; i < ii; i++) { 
  var callback = pending[i]; 
  callback(value); 
 } 
 pending = undefined; 
 } else { 
 throw new Error("A promise can only be resolved once."); 
 } 
} 

对第二次及更多的调用,可以这样抛出一个错误,也可以直接忽略掉。

分离defer和promise

在前面的实现中,defer生成的对象同时拥有then方法和resolve方法。按照定义,promise关心的是then方法,至于触发promise改变状态的resolve,是另一回事。所以,Q接下来将拥有then方法的promise,和拥有resolve的defer分离开来,各自独立使用。这样就好像划清了各自的职责,各自只留一定的权限,这会使代码逻辑更明晰,易于调整。请看design/q3.js:(q2在此跳过)

var isPromise = function (value) { 
 return value && typeof value.then === "function"; 
}; 
 
var defer = function () { 
 var pending = [], value; 
 return { 
 resolve: function (_value) { 
  if (pending) { 
  value = _value; 
  for (var i = 0, ii = pending.length; i < ii; i++) { 
   var callback = pending[i]; 
   callback(value); 
  } 
  pending = undefined; 
  } 
 }, 
 promise: { 
  then: function (callback) { 
  if (pending) { 
   pending.push(callback); 
  } else { 
   callback(value); 
  } 
  } 
 } 
 }; 
}; 

如果你仔细对比一下q1,你会发现区别很小。一方面,不再抛出错误(改为直接忽略第二次及更多的resolve),另一方面,将then方法移动到一个名为promise的对象内。到这里,运行defer()得到的对象(就称为defer吧),将拥有resolve方法,和一个promise属性指向另一个对象。这另一个对象就是仅有then方法的promise。这就完成了分离。

前面还有一个isPromise()函数,它通过是否有then方法来判断对象是否是promise(duck-typing的判断方法)。为了正确使用和处理分离开的promise,会像这样需要将promise和其他值区分开来。


实现promise的级联

接下来会是相当重要的一步。到前面到q3为止,所实现的promise都是不能级联的。但你所熟知的promise应该支持这样的语法:

复制代码 代码如下:
promise.then(step1).then(step2);

以上过程可以理解为,promise将可以创造新的promise,且取自旧的promise的值(前面代码中的value)。要实现then的级联,需要做到一些事情:

then方法必须返回promise。

这个返回的promise必须用传递给then方法的回调运行后的返回结果,来设置自己的值。

传递给then方法的回调,必须返回一个promise或值。

design/q4.js中,为了实现这一点,新增了一个工具函数ref:

复制代码 代码如下:
var ref = function (value) { 
  if (value && typeof value.then === "function") 
    return value; 
  return { 
    then: function (callback) { 
      return ref(callback(value)); 
    } 
  }; 
}; 

这是在着手处理与promise关联的value。这个工具函数将对任一个value值做一次包装,如果是一个promise,则什么也不做,如果不是promise,则将它包装成一个promise。注意这里有一个递归,它确保包装成的promise可以使用then方法级联。为了帮助理解它,下面是一个使用的例子:

复制代码 代码如下:
ref("step1").then(function(value){ 
  console.log(value); // "step1" 
  return 15; 
}).then(function(value){ 
  console.log(value); // 15 
});

你可以看到value是怎样传递的,promise级联需要做到的也是如此。

design/q4.js通过结合使用这个ref函数,将原来的defer转变为可级联的形式:

var defer = function () { 
 var pending = [], value; 
 return { 
 resolve: function (_value) { 
  if (pending) { 
  value = ref(_value); // values wrapped in a promise 
  for (var i = 0, ii = pending.length; i < ii; i++) { 
   var callback = pending[i]; 
   value.then(callback); // then called instead 
  } 
  pending = undefined; 
  } 
 }, 
 promise: { 
  then: function (_callback) { 
  var result = defer(); 
  // callback is wrapped so that its return 
  // value is captured and used to resolve the promise 
  // that "then" returns 
  var callback = function (value) { 
   result.resolve(_callback(value)); 
  }; 
  if (pending) { 
   pending.push(callback); 
  } else { 
   value.then(callback); 
  } 
  return result.promise; 
  } 
 } 
 }; 
}; 

原来callback(value)的形式,都修改为value.then(callback)。这个修改后效果其实和原来相同,只是因为value变成了promise包装的类型,会需要这样调用。

then方法有了较多变动,会先新生成一个defer,并在结尾处返回这个defer的promise。请注意,callback不再是直接取用传递给then的那个,而是在此基础之上增加一层,并把新生成的defer的resolve方法放置在此。此处可以理解为,then方法将返回一个新生成的promise,因此需要把promise的resolve也预留好,在旧的promise的resolve运行后,新的promise的resolve也会随之运行。这样才能像管道一样,让事件按照then连接的内容,一层一层传递下去。


加入错误处理

promise的then方法应该可以包含两个参数,分别是肯定和否定状态的处理函数(onFulfilled与onRejected)。前面我们实现的promise还只能转变为肯定状态,所以,接下来应该加入否定状态部分。

请注意,promise的then方法的两个参数,都是可选参数。design/q6.js(q5也跳过)加入了工具函数reject来帮助实现promise的否定状态:

var reject = function (reason) { 
 return { 
 then: function (callback, errback) { 
  return ref(errback(reason)); 
 } 
 }; 
}; 

它和ref的主要区别是,它返回的对象的then方法,只会取第二个参数的errback来运行。design/q6.js的其余部分是:

var defer = function () { 
 var pending = [], value; 
 return { 
 resolve: function (_value) { 
  if (pending) { 
  value = ref(_value); 
  for (var i = 0, ii = pending.length; i < ii; i++) { 
   value.then.apply(value, pending[i]); 
  } 
  pending = undefined; 
  } 
 }, 
 promise: { 
  then: function (_callback, _errback) { 
  var result = defer(); 
  // provide default callbacks and errbacks 
  _callback = _callback || function (value) { 
   // by default, forward fulfillment 
   return value; 
  }; 
  _errback = _errback || function (reason) { 
   // by default, forward rejection 
   return reject(reason); 
  }; 
  var callback = function (value) { 
   result.resolve(_callback(value)); 
  }; 
  var errback = function (reason) { 
   result.resolve(_errback(reason)); 
  }; 
  if (pending) { 
   pending.push([callback, errback]); 
  } else { 
   value.then(callback, errback); 
  } 
  return result.promise; 
  } 
 } 
 }; 
}; 

这里的主要改动是,将数组pending只保存单个回调的形式,改为同时保存肯定和否定的两种回调的形式。而且,在then中定义了默认的肯定和否定回调,使得then方法满足了promise的2个可选参数的要求。

你也许注意到defer中还是只有一个resolve方法,而没有类似jQuery的reject。那么,错误处理要如何触发呢?请看这个例子:

var defer1 = defer(), 
promise1 = defer1.promise; 
promise1.then(function(value){ 
 console.log("1: value = ", value); 
 return reject("error happens"); 
}).then(function(value){ 
 console.log("2: value = ", value); 
}).then(null, function(reason){ 
 console.log("3: reason = ", reason); 
}); 
defer1.resolve(10); 
 
// Result: 
// 1: value = 10 
// 3: reason = error happens 

可以看出,每一个传递给then方法的返回值是很重要的,它将决定下一个then方法的调用结果。而如果像上面这样返回工具函数reject生成的对象,就会触发错误处理。


融入异步

终于到了最后的design/q7.js。直到前面的q6,还存在一个问题,就是then方法运行的时候,可能是同步的,也可能是异步的,这取决于传递给then的函数(例如直接返回一个值,就是同步,返回一个其他的promise,就可以是异步)。这种不确定性可能带来潜在的问题。因此,Q的后面这一步,是确保将所有then转变为异步。

design/q7.js定义了另一个工具函数enqueue:

复制代码 代码如下:
var enqueue = function (callback) { 
  //process.nextTick(callback); // NodeJS 
  setTimeout(callback, 1); // Na?ve browser solution 
};


显然,这个工具函数会将任意函数推迟到下一个事件队列运行。

design/q7.js其他的修改点是(只显示修改部分):

var ref = function (value) { 
 // ... 
 return { 
 then: function (callback) { 
  var result = defer(); 
  // XXX 
  enqueue(function () { 
  result.resolve(callback(value)); 
  }); 
  return result.promise; 
 } 
 }; 
}; 
 
var reject = function (reason) { 
 return { 
 then: function (callback, errback) { 
  var result = defer(); 
  // XXX 
  enqueue(function () { 
  result.resolve(errback(reason)); 
  }); 
  return result.promise; 
 } 
 }; 
}; 
 
var defer = function () { 
 var pending = [], value; 
 return { 
 resolve: function (_value) { 
  // ... 
   enqueue(function () { 
   value.then.apply(value, pending[i]); 
   }); 
  // ... 
 }, 
 promise: { 
  then: function (_callback, _errback) { 
   // ... 
   enqueue(function () { 
   value.then(callback, errback); 
   }); 
   // ... 
  } 
 } 
 }; 
}; 

即把原来的value.then的部分,都转变为异步。

到此,Q提供的Promise设计原理q0~q7,全部结束。


结语

即便本文已经是这么长的篇幅,但所讲述的也只到基础的Promise。大部分Promise库会有更多的API来应对更多和Promise有关的需求,例如all()、spread(),不过,读到这里,你已经了解了实现Promise的核心理念,这一定对你今后应用Promise有所帮助。

在我看来,Promise是精巧的设计,我花了相当一些时间才差不多理解它。Q作为一个典型Promise库,在思路上走得很明确。可以感受到,再复杂的库也是先从基本的要点开始的,如果我们自己要做类似的事,也应该保持这样的心态一点一点进步。

以上就是本文的全部内容,希望对大家的学习有所帮助。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
JavaScript框架:為現代網絡開發提供動力JavaScript框架:為現代網絡開發提供動力May 02, 2025 am 12:04 AM

JavaScript框架的強大之處在於簡化開發、提升用戶體驗和應用性能。選擇框架時應考慮:1.項目規模和復雜度,2.團隊經驗,3.生態系統和社區支持。

JavaScript,C和瀏覽器之間的關係JavaScript,C和瀏覽器之間的關係May 01, 2025 am 12:06 AM

引言我知道你可能會覺得奇怪,JavaScript、C 和瀏覽器之間到底有什麼關係?它們之間看似毫無關聯,但實際上,它們在現代網絡開發中扮演著非常重要的角色。今天我們就來深入探討一下這三者之間的緊密聯繫。通過這篇文章,你將了解到JavaScript如何在瀏覽器中運行,C 在瀏覽器引擎中的作用,以及它們如何共同推動網頁的渲染和交互。 JavaScript與瀏覽器的關係我們都知道,JavaScript是前端開發的核心語言,它直接在瀏覽器中運行,讓網頁變得生動有趣。你是否曾經想過,為什麼JavaScr

node.js流帶打字稿node.js流帶打字稿Apr 30, 2025 am 08:22 AM

Node.js擅長於高效I/O,這在很大程度上要歸功於流。 流媒體匯總處理數據,避免內存過載 - 大型文件,網絡任務和實時應用程序的理想。將流與打字稿的類型安全結合起來創建POWE

Python vs. JavaScript:性能和效率注意事項Python vs. JavaScript:性能和效率注意事項Apr 30, 2025 am 12:08 AM

Python和JavaScript在性能和效率方面的差異主要體現在:1)Python作為解釋型語言,運行速度較慢,但開發效率高,適合快速原型開發;2)JavaScript在瀏覽器中受限於單線程,但在Node.js中可利用多線程和異步I/O提升性能,兩者在實際項目中各有優勢。

JavaScript的起源:探索其實施語言JavaScript的起源:探索其實施語言Apr 29, 2025 am 12:51 AM

JavaScript起源於1995年,由布蘭登·艾克創造,實現語言為C語言。 1.C語言為JavaScript提供了高性能和系統級編程能力。 2.JavaScript的內存管理和性能優化依賴於C語言。 3.C語言的跨平台特性幫助JavaScript在不同操作系統上高效運行。

幕後:什麼語言能力JavaScript?幕後:什麼語言能力JavaScript?Apr 28, 2025 am 12:01 AM

JavaScript在瀏覽器和Node.js環境中運行,依賴JavaScript引擎解析和執行代碼。 1)解析階段生成抽象語法樹(AST);2)編譯階段將AST轉換為字節碼或機器碼;3)執行階段執行編譯後的代碼。

Python和JavaScript的未來:趨勢和預測Python和JavaScript的未來:趨勢和預測Apr 27, 2025 am 12:21 AM

Python和JavaScript的未來趨勢包括:1.Python將鞏固在科學計算和AI領域的地位,2.JavaScript將推動Web技術發展,3.跨平台開發將成為熱門,4.性能優化將是重點。兩者都將繼續在各自領域擴展應用場景,並在性能上有更多突破。

Python vs. JavaScript:開發環境和工具Python vs. JavaScript:開發環境和工具Apr 26, 2025 am 12:09 AM

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。