今回は、徹底的に約束を理解します

約束の3つの状態のいずれかでなければなりません、(保留)状態を待って実行状態(成就)及び(拒否)の状態を拒否しました。プロミスが解決または拒否されると、他の状態(すなわち、状態不変)に移行することができません。

基本的なプロセス:

  1. 約束初期化状態(ペンディング)

  2. 実行は、次いで、(..)コールバック処理アレイを登録する(この方法は有望で何度も呼び出すことができます)

  3. 実行直後に渡されたFN機能の約束は、内部プロミスの決意は、イベント処理メカニズムのタイミングに応じて、fnの引数として渡された関数を拒否します

  4. キーは、メソッドとonRejectedをonFulfilled入ってくるのパラメータは、それがイベントループメソッドが呼び出された中で、そのラウンドの後に、新たな実行スタックで実行する必要があり、確実にするためである約束。

本当のチェーンの約束は、現在の約束は次の約束を開始した状態に到達するために満たされた後ということを意味します。

チェーンの呼び出し

約束の結果、以下のコードを見て起動します。

 新しいプロミス((決意、リジェクト)=> { 
 たsetTimeout(()=> { 
 解決({テスト:1})
 解決({テスト:2})
 2}):拒否({試験
 }、1000) 
 、次いで})。 ((データ)=> { 
 にconsole.log( '結果1'、データ)
 }、(DATA1)=> { 
 にconsole.log( '結果2'、DATA1)
 })。次に、((データ)=> { 
 にconsole.log( 'result3'、データ)
 })
 //結果1 {テスト:1} 
 // result3未定義
复制代码

明らかに、この出力の異なるデータ。それはそれを見ることができます。

  1. コールは連鎖し、かつそれが同じインスタンスである場合には、新たな約束(2回の印刷結果は、矛盾するたびに返され、結果は一貫している必要があります印刷することができます。

  2. 拒否>成就やpending->、それは不可逆的である - 出力のみ最初のコンテンツを解決し、拒否したコンテンツは、すなわち、国家と国家の約束だけ保留行うことがあり、何も出力されません。

  3. その後、新しい約束に戻ったが、その後、まだ約束に属しているコールバックを登録。

以上を踏まえ、我々は最初の書き込みベースPromiseA +の約束モデルの仕様は唯一の解決方法が含まれています。

 約束関数(FN){  
 せSTATE = '保留'; 
 LET値= NULL; 
 コールバック= [] CONST; 
 this.then =関数(onFulfilled){ 
 拒否、解決((新しい新しい約束を返すを)=> { 
 ハンドル({//ブリッジは、プロミスを解決する新しい方法は、コールバックオブジェクトは、約束の前に置か
 onFulfilled、  
 解決
 })
 })
 } 
 関数ハンドル(コールバック){ 
 ===「保留」){IF(状態
 callbacks.push(コールバック)
 リターン; 
 } 
 
 IF(状態=== '成就'){ 
 IF {(callback.onFulfilled!)
 callback.resolve(値)
 のリターン; 
 } 
 CONST callback.onFulfilled RET =(値)//コールバック処理
 callback.resolveを(RET)/次/プロセスを解決することを約束 
 } 
 }
 機能解決(newValueに){ 
 constのFN =()=> { 
 場合(!状態== '保留')戻り
 状態=は'成就'; 
 値= newValueに
 handelCb()
 } 
 
 のsetTimeout(FN、0)//基于PromiseA +规范
 } 
 
 関数handelCb(){ 
 一方(callbacks.length){ 
 CONST fulfiledFn = callbacks.shift()。
 (fulfiledFn)を扱います。
 }。
 } 
 
 FN(解決)
 } 
复制代码

モデルはシンプルで理解しやすい、約束が、これはその後、新しく作成された中で最も重要なポイントであり、その状態が完了したときに、実行の約束のコールバックで満たさノードに変更されます。状態プロミスが満たされた場合には、それはコールバック関数を実行され、コールバック関数によって返される結果は次の約束しながら、次の約束する(約束は、生成された)値が、リターンとして扱われます状態も(決意を実行または拒否)、そのコールバックを実行して行く、というように連鎖効果ダウンに変更されます...呼び出しが出てきました。

しかし、唯一の例の場合では、我々は書くことができる場合:

 新しいプロミス((決意、リジェクト)=> { 
 たsetTimeout(()=> { 
 解決({テスト:1})
 }、1000)
 })。次に、((データ)=> { 
 にconsole.log( '結果1'、データ)
 // doSomethingの
 はconsole.log( 'result3')
 })
 //結果1 {テスト:1} 
 // result3 
复制代码

実際には、我々は、チェーンを呼び出すために使用される、「コールバック地獄」の問題を解決するために、非同期コールバックで使用されています。例としては、次のとおりです:

({(解決、拒否)=>新しい新しい約束
 のsetTimeout(()=> { 
 解決({テスト:. 1})
 1000})
})を((データ)=> { 
 にconsole.log( '結果1'、データ)
 // doSomethingの
 テストを返す()
})。次に、((データ)=> { 
 にconsole.log( '結果2'、データ)
})
機能テスト(ID){ 
 新しい新しいプロミス(((解決返す)=> { 
 (のsetTimeoutを()=> { 
 解決({テスト:2})
 }、5000)
 }))
} 
//約束最初のモデルに基づいて、実行を出力
//テスト結果1 {:} 1 
// {次いで約束結果2:ƒ } 
コードをコピー

上記のモデルを使用した約束は、結果は我々が望むもの、明らかではありません。真剣に上記のモデルを見て、callback.resolveの実装は、入ってくるパラメータはcallback.onFulfilled実行が完了返され、テストケースは我々のモデルは特別扱いではない約束、約束と解決方法を返すでは明らかです。その後、我々はそれを変更し解決します。

 プロミス機能(FN){  
 ... 
 関数解決(newValueに){ 
 constののFn =()=> { 
 IF(州立!== '保留')リターン
 IF(newValueに&&(typeofをnewValueに=== 'オブジェクト' || typeof演算=== newValueに「関数」)){ 
 CONST = {} newValueに、次いで
 (その後===「機能が」){typeof演算IF 
 // newValueに約束を新たに決意を解決するために約束をこの時点で生成される
 //等価方法は、次に、新たに生成された約束は、そのコールバック解決する約束に注入呼び出し
 then.call(newValueに、解決)
 リターン
 } 
 } 
 状態=「成就を」; 
 値= newValueに
 handelCb()
 } 
 
 のsetTimeout(FN、0)
 } 
 ... 
 } 
コードをコピー

このモデルでは、その後、私たちの例をテスト、我々は正しい結果が得られます。

 新しいプロミス((決意、リジェクト)=> { 
 たsetTimeout(()=> { 
 解決({テスト:1})
 }、1000)
 })。次に、((データ)=> { 
 にconsole.log( '結果1'、データ)
 //のdoSomethingの
 リターン試験()
 })。次に、((データ)=> { 
 にconsole.log( '結果2'、データ)
 })
 機能試験(ID){ 
 戻り、新たな約束(((決意、リジェクト)=> { 
 setTimeout(()=> { 
 解決({テスト:2})
 }、5000)
 }))
 } 
 //結果1 {テスト:1} 
 //結果2 {テスト:2} 
复制代码

明らかに、新しいロジックは、時間の約束のための処理パラメータを解決することを目的としています。私たちは、それが法と呼ばれていない、作成した内部の約束のテストを見て。上記の分析から、我々は、既に約束のコールバック関数は、そのメソッドを呼び出すことにより、登録されている知っているので、そのコールバック関数が空で作成した約束をテストします。

コールバック関数が存在しない場合決意を実行したときに明らかに、チェーンはどこへ行く方法はありません。したがって、我々は、コールバック関数を注入するためのイニシアチブを取る必要があります。

限り、我々は機能を実行するために解決するように最初に約束、再実行onFulfilled状態のプロミス内部テストに遅れを生じ、連鎖を継続することができます。だから、ときにプロミスresolveメソッドの前に注入されるコールバック関数を、注入するためのメソッドを呼び出し、時間の約束のためのパラメータを解決するので、この点をバインドするための呼び出しを使用します。

新しいモデルに基づく約束、および実施例は、上記の実行中に発生したコールバックは以下の表を使用することができる約束します。

[{onFulfilled:C1(FN最初)、解決:p2resolve}]約束コールバックP1とP2を[(呼ときP1が生成される){onFulfilled:(第2 FN中)c2は、解決:p3resolve} 】P3(P2は、その後呼び出しを生成)] P4(C1発生実行[テストを呼び出す]){onFulfilled:p2resolveは、解決:p5resolveを}(生成された論理then.callにp2resolveを呼び出す)] P5 []はこの表には、我々は明らかにコールバックがある様々な例の実行順序を知ることができます。

C1 - > p2resolve - > C2 - > p3resolve - > [] - > p5resolve - > []

これらは、連鎖呼び出しの原則です。

拒絶します

ここで私たちは完全に補完ロジックを拒否してみましょう。あなただけが変更を拒否するために、コールバック、結合された論理状態を登録する必要があります。

次のように完全なコードは次のとおりです。

 function Promise(fn){ 
 let state = 'pending';
 let value = null;
 const callbacks = [];
 this.then = function (onFulfilled,onRejected){
 return new Promise((resolve, reject)=>{
 handle({
 onFulfilled, 
 onRejected,
 resolve, 
 reject
 })
 })
 }
 function handle(callback){
 if(state === 'pending'){
 callbacks.push(callback)
 return;
 }
 
 const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
 const next = state === 'fulfilled'? callback.resolve:callback.reject;
 if(!cb){
 next(value)
 return;
 }
 const ret = cb(value)
 next(ret)
 }
 function resolve(newValue){
 const fn = ()=>{
 if(state !== 'pending')return
 if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
 const {then} = newValue
 if(typeof then === 'function'){
 // newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
 //相当于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
 then.call(newValue,resolve, reject)
 return
 }
 }
 state = 'fulfilled';
 value = newValue
 handelCb()
 }
 
 setTimeout(fn,0)
 }
 function reject(error){
 const fn = ()=>{
 if(state !== 'pending')return
 if(error && (typeof error === 'object' || typeof error === 'function')){
 const {then} = error
 if(typeof then === 'function'){
 then.call(error,resolve, reject)
 return
 }
 }
 state = 'rejected';
 value = error
 handelCb()
 }
 setTimeout(fn,0)
 }
 function handelCb(){
 while(callbacks.length) {
 const fn = callbacks.shift();
 handle(fn);
 };
 }
 fn(resolve, reject)
 }
复制代码

异常处理

异常通常是指在执行成功/失败回调时代码出错产生的错误,对于这类异常,我们使用 try-catch 来捕获错误,并将 Promise 设为 rejected 状态即可。

handle代码改造如下:

 function handle(callback){
 if(state === 'pending'){
 callbacks.push(callback)
 return;
 }
 
 const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
 const next = state === 'fulfilled'? callback.resolve:callback.reject;
 if(!cb){
 next(value)
 return;
 }
 try {
 const ret = cb(value)
 next(ret)
 } catch (e) {
 callback.reject(e);
 } 
 }
复制代码

我们实际使用时,常习惯注册 catch 方法来处理错误,例:

 new Promise((resolve, reject) => {
 setTimeout(() => {
 resolve({ test: 1 })
 }, 1000)
 }).then((data) => {
 console.log('result1', data)
 //dosomething
 return test()
 }).catch((ex) => {
 console.log('error', ex)
 })
复制代码

实际上,错误也好,异常也罢,最终都是通过reject实现的。也就是说可以通过 then 中的错误回调来处理。所以我们可以增加这样的一个 catch 方法:

 function Promise(fn){ 
 ...
 this.then = function (onFulfilled,onRejected){
 return new Promise((resolve, reject)=>{
 handle({
 onFulfilled, 
 onRejected,
 resolve, 
 reject
 })
 })
 }
 this.catch = function (onError){
 this.then(null,onError)
 }
 ...
 }
复制代码

Finally方法

在实际应用的时候,我们很容易会碰到这样的场景,不管Promise最后的状态如何,都要执行一些最后的操作。我们把这些操作放到 finally 中,也就是说 finally 注册的函数是与 Promise 的状态无关的,不依赖 Promise 的执行结果。所以我们可以这样写 finally 的逻辑:

 function Promise(fn){ 
 ...
 this.catch = function (onError){
 this.then(null,onError)
 }
 this.finally = function (onDone){
 this.then(onDone,onDone)
 }
 ...
 }
复制代码

resolve 方法和 reject 方法

实际应用中,我们可以使用 Promise.resolve 和 Promise.reject 方法,用于将于将非 Promise 实例包装为 Promise 实例。如下例子:

Promise.resolve({name:'winty'})
Promise.reject({name:'winty'})
// 等价于
new Promise(resolve => resolve({name:'winty'}))
new Promise((resolve,reject) => reject({name:'winty'}))
复制代码

这些情况下,Promise.resolve 的入参可能有以下几种情况:

  • 无参数 [直接返回一个resolved状态的 Promise 对象]

  • 普通数据对象 [直接返回一个resolved状态的 Promise 对象]

  • 一个Promise实例 [直接返回当前实例]

  • 一个thenable对象(thenable对象指的是具有then方法的对象) [转为 Promise 对象,并立即执行thenable对象的then方法。]

基于以上几点,我们可以实现一个 Promise.resolve 方法如下:

 function Promise(fn){ 
 ...
 this.resolve = function (value){
 if (value && value instanceof Promise) {
 return value;
 } else if (value && typeof value === 'object' && typeof value.then === 'function'){
 let then = value.then;
 return new Promise(resolve => {
 then(resolve);
 });
 } else if (value) {
 return new Promise(resolve => resolve(value));
 } else {
 return new Promise(resolve => resolve());
 }
 }
 ...
 }
复制代码

Promise.reject与Promise.resolve类似,区别在于Promise.reject始终返回一个状态的rejected的Promise实例,而Promise.resolve的参数如果是一个Promise实例的话,返回的是参数对应的Promise实例,所以状态不一 定。 因此,reject 的实现就简单多了,如下:

 function Promise(fn){ 
 ...
 this.reject = function (value){
 return new Promise(function(resolve, reject) {
				reject(value);
			});
 }
 ...
 }
复制代码

Promise.all

入参是一个 Promise 的实例数组,然后注册一个 then 方法,然后是数组中的 Promise 实例的状态都转为 fulfilled 之后则执行 then 方法。这里主要就是一个计数逻辑,每当一个 Promise 的状态变为 fulfilled 之后就保存该实例返回的数据,然后将计数减一,当计数器变为 0 时,代表数组中所有 Promise 实例都执行完毕。

 function Promise(fn){ 
 ...
 this.all = function (arr){
 var args = Array.prototype.slice.call(arr);
 return new Promise(function(resolve, reject) {
 if(args.length === 0) return resolve([]);
 var remaining = args.length;
 function res(i, val) {
 try {
 if(val && (typeof val === 'object' || typeof val === 'function')) {
 var then = val.then;
 if(typeof then === 'function') {
 then.call(val, function(val) {
 res(i, val);
 }, reject);
 return;
 }
 }
 args[i] = val;
 if(--remaining === 0) {
 resolve(args);
 }
 } catch(ex) {
 reject(ex);
 }
 }
 for(var i = 0; i < args.length; i++) {
 res(i, args[i]);
 }
 });
 }
 ...
 }
复制代码

Promise.race

有了 Promise.all 的理解,Promise.race 理解起来就更容易了。它的入参也是一个 Promise 实例数组,然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行。因为 Promise 的状态只能改变一次,那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法,注入到数组中的每一个 Promise 实例中的回调函数中即可。

function Promise(fn){ 
 ...
 this.race = function(values) {
 return new Promise(function(resolve, reject) {
 for(var i = 0, len = values.length; i < len; i++) {
 values[i].then(resolve, reject);
 }
 });
 }
 ...
 } 
复制代码

总结

Promise 源码不过几百行,我们可以从执行结果出发,分析每一步的执行过程,然后思考其作用即可。其中最关键的点就是要理解 then 函数是负责注册回调的,真正的执行是在 Promise 的状态被改变之后。而当 resolve 的入参是一个 Promise 时,要想链式调用起来,就必须调用其 then 方法(then.call),将上一个 Promise 的 resolve 方法注入其回调数组中。

补充说明

虽然 then 普遍认为是微任务。但是浏览器没办法模拟微任务,目前要么用 setImmediate ,这个也是宏任务,且不兼容的情况下还是用 setTimeout 打底的。还有,promise 的 polyfill (es6-promise) 里用的也是 setTimeout。因此这里就直接用 setTimeout,以宏任务来代替微任务了。

参考资料

  • PromiseA+规范

  • Promise 实现原理精解

  • 30分钟,让你彻底明白Promise原理

完整 Promise 模型

function Promise(fn) {
 let state = 'pending'
 let value = null
 const callbacks = []
 this.then = function (onFulfilled, onRejected) {
 return new Promise((resolve, reject) => {
 handle({
 onFulfilled,
 onRejected,
 resolve,
 reject,
 })
 })
 }
 this.catch = function (onError) {
 this.then(null, onError)
 }
 this.finally = function (onDone) {
 this.then(onDone, onError)
 }
 this.resolve = function (value) {
 if (value && value instanceof Promise) {
 return value
 } if (value && typeof value === 'object' && typeof value.then === 'function') {
 const { then } = value
 return new Promise((resolve) => {
 then(resolve)
 })
 } if (value) {
 return new Promise(resolve => resolve(value))
 }
 return new Promise(resolve => resolve())
 }
 this.reject = function (value) {
 return new Promise(((resolve, reject) => {
 reject(value)
 }))
 }
 this.all = function (arr) {
 const args = Array.prototype.slice.call(arr)
 return new Promise(((resolve, reject) => {
 if (args.length === 0) return resolve([])
 let remaining = args.length
 function res(i, val) {
 try {
 if (val && (typeof val === 'object' || typeof val === 'function')) {
 const { then } = val
 if (typeof then === 'function') {
 then.call(val, (val) => {
 res(i, val)
 }, reject)
 return
 }
 }
 args[i] = val
 if (--remaining === 0) {
 resolve(args)
 }
 } catch (ex) {
 reject(ex)
 }
 }
 for (let i = 0; i < args.length; i++) {
 res(i, args[i])
 }
 }))
 }
 this.race = function (values) {
 return new Promise(((resolve, reject) => {
 for (let i = 0, len = values.length; i < len; i++) {
 values[i].then(resolve, reject)
 }
 }))
 }
 function handle(callback) {
 if (state === 'pending') {
 callbacks.push(callback)
 return
 }
 const cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected
 const next = state === 'fulfilled' ? callback.resolve : callback.reject
 if (!cb) {
 next(value)
 return
 }
 try {
 const ret = cb(value)
 next(ret)
 } catch (e) {
 callback.reject(e)
 }
 }
 function resolve(newValue) {
 const fn = () => {
 if (state !== 'pending') return
 if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
 const { then } = newValue
 if (typeof then === 'function') {
 // newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
 // 相当于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
 then.call(newValue, resolve, reject)
 return
 }
 }
 state = 'fulfilled'
 value = newValue
 handelCb()
 }
 setTimeout(fn, 0)
 }
 function reject(error) {
 const fn = () => {
 if (state !== 'pending') return
 if (error && (typeof error === 'object' || typeof error === 'function')) {
 const { then } = error
 if (typeof then === 'function') {
 then.call(error, resolve, reject)
 return
 }
 }
 state = 'rejected'
 value = error
 handelCb()
 }
 setTimeout(fn, 0)
 }
 function handelCb() {
 while (callbacks.length) {
 const fn = callbacks.shift()
 handle(fn)
 }
 }
 fn(resolve, reject)
}

这一次,彻底弄懂 Promise


おすすめ

転載: blog.51cto.com/14516511/2437097