ホームページ ウェブフロントエンド jsチュートリアル JavaScriptプログラミングにおけるPromiseの徹底活用_基礎知識

JavaScriptプログラミングにおけるPromiseの徹底活用_基礎知識

May 16, 2016 pm 03:48 PM
javascript promise 非同期プログラミング

Promise には既に独自の仕様がありますが、現在の各 Promise ライブラリは Promise の実装内容に違いがあり、API によっては全く意味が異なっているものもあります。ただし、Promise の核となる内容は同じであり、then メソッドです。これに関連した用語では、Promise は、特定の動作をトリガーできる then メソッドを持つオブジェクトまたは関数を指します。

Promise はさまざまな方法で実装できるため、Promise のコアの説明では特定の実装コードについては説明しません。

最初に Promise のコアの説明を読んでください。これは、コードに記述する方法を検討するためにこの結果を参照してください。

開始: Promise をこのように理解する

Promise が解決する問題を思い出してください。折り返し電話。たとえば、関数 doMission1() は最初のことを表します。このことが完了した後、次のこと doMission2() を実行する必要があります。

まず、一般的なコールバック パターンを見てみましょう。 doMission1() は次のように言いました: 「これをやりたい場合は doMission2() を与えてください。完了したら私が呼び出します。」 したがって、次のようになります。

コードをコピーします コードは次のとおりです。
doMission1(doMission2);
Promise モードについてはどうですか?あなたは doMission1() に対して次のように言いました。「いいえ、コントロールは私にあります。あなたはそれを変更する必要があります。あなたは最初に特別なものを私に返します。そして私はこの特別なものを使って次のものを手配します。」これは次のようになります:

コードをコピーします コードは次のとおりです:doMission1().then(doMission2);
Promise がコールバック モードのマスターとスレーブの関係を変更する (裏返ってマスターになる!) ことがわかります。複数のイベントのプロセス関係が (さまざまなイベントに分散するのではなく) 主要な道路に集中することができます。機能)。
それでは、そのような変換を行うにはどうすればよいでしょうか? doMission1() のコードが次であると仮定して、最も単純なケースから始めましょう:

コードをコピー コードは次のとおりです。function doMission1(callback){ 変数値 = 1; コールバック(値);

その後、次のように変更できます:

コードをコピー

コードは次のとおりです。function doMission1(){ リターン { 次に: function(callback){ 変数値 = 1; コールバック(値);
}
};
}


これで変換は完了です。実際に役立つ変換ではありませんが、ここでは実際に Promise の最も重要な実装ポイント、つまり Promise が then メソッドを使用して戻り値をオブジェクトに変換することについて触れました。


上級: Q のデザインの旅

延期から開始

design/q0.js は、Q の初期形成の最初のステップです。 Promise を作成するための defer というユーティリティ関数を作成します:

このソース コードからわかるように、defer() を実行すると、resolve と then の 2 つのメソッドを含むオブジェクトが取得されます。 jQuery の Deferred (resolve と then も) を思い出してください。これら 2 つのメソッドは同様の効果があります。次に、保留状態を参照し、待機状態であればコールバックを保存(プッシュ)し、それ以外の場合はすぐにコールバックを呼び出します。 solve は Promise を確認し、値を更新し、保存されているすべてのコールバックを同時に実行します。遅延の使用例は次のとおりです:

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); 
  } 
 } 
 } 
}; 
ログイン後にコピー

コードをコピー

コードは次のとおりです。var oneOneSecondLater = function () { var result = defer(); setTimeout(function() { 結果.resolve(1); }, 1000); 結果を返します };


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 までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

C++ 関数を使用して非同期プログラミングを実装するにはどうすればよいですか? C++ 関数を使用して非同期プログラミングを実装するにはどうすればよいですか? Apr 27, 2024 pm 09:09 PM

概要: C++ の非同期プログラミングを使用すると、時間のかかる操作を待たずにマルチタスクを行うことができます。関数ポインターを使用して、関数へのポインターを作成します。コールバック関数は、非同期操作が完了すると呼び出されます。 boost::asio などのライブラリは、非同期プログラミングのサポートを提供します。実際のケースでは、関数ポインターと boost::asio を使用して非同期ネットワーク リクエストを実装する方法を示します。

約束を守る: 約束を守ることの長所と短所 約束を守る: 約束を守ることの長所と短所 Feb 18, 2024 pm 08:06 PM

日常生活では、約束と履行の間で問題に遭遇することがよくあります。個人的な関係でもビジネス取引でも、約束を守ることが信頼を築く鍵となります。ただし、コミットメントの是非についてはしばしば議論の余地があります。この記事では、約束の長所と短所を検討し、約束を守る方法についていくつかのアドバイスを提供します。約束されたメリットは明らかです。まず、コミットメントは信頼を築きます。人が約束を守るとき、その人は信頼できる人であると他人に信じ込ませます。信頼は人々の間に確立される絆であり、それは人々をより良くすることができます

簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 Jan 05, 2024 pm 06:08 PM

JavaScript チュートリアル: HTTP ステータス コードを取得する方法、特定のコード例が必要です 序文: Web 開発では、サーバーとのデータ対話が頻繁に発生します。サーバーと通信するとき、多くの場合、返された HTTP ステータス コードを取得して操作が成功したかどうかを判断し、さまざまなステータス コードに基づいて対応する処理を実行する必要があります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法を説明し、いくつかの実用的なコード例を示します。 XMLHttpRequestの使用

Promise.resolve() について詳しく見る Promise.resolve() について詳しく見る Feb 18, 2024 pm 07:13 PM

Promise.resolve() の詳細な説明には、特定のコード例が必要です。Promise は、非同期操作を処理するための JavaScript のメカニズムです。実際の開発では、順番に実行する必要があるいくつかの非同期タスクを処理する必要があることがよくあり、満たされた Promise オブジェクトを返すために Promise.resolve() メソッドが使用されます。 Promise.resolve() は Promise クラスの静的メソッドであり、

Java フレームワークでの非同期プログラミングにおける一般的な問題と解決策 Java フレームワークでの非同期プログラミングにおける一般的な問題と解決策 Jun 04, 2024 pm 05:09 PM

Java フレームワークでの非同期プログラミングにおける 3 つの一般的な問題と解決策: コールバック地獄: Promise または CompletableFuture を使用して、より直感的なスタイルでコールバックを管理します。リソースの競合: 同期プリミティブ (ロックなど) を使用して共有リソースを保護し、スレッドセーフなコレクション (ConcurrentHashMap など) の使用を検討します。未処理の例外: タスク内の例外を明示的に処理し、例外処理フレームワーク (CompletableFuture.Exceptionally() など) を使用して例外を処理します。

golang フレームワークは同時実行性と非同期プログラミングをどのように処理しますか? golang フレームワークは同時実行性と非同期プログラミングをどのように処理しますか? Jun 02, 2024 pm 07:49 PM

Go フレームワークは Go の同時実行性と非同期機能を使用して、同時タスクと非同期タスクを効率的に処理するためのメカニズムを提供します。 1. 同時実行性は Goroutine によって実現され、複数のタスクを同時に実行できます。 2. 非同期プログラミングはチャネルを通じて実装されます。メインスレッドをブロックせずに実行可能。 3. HTTP リクエストの同時処理、データベース データの非同期取得などの実用的なシナリオに適しています。

JavaScript で HTTP ステータス コードを簡単に取得する方法 JavaScript で HTTP ステータス コードを簡単に取得する方法 Jan 05, 2024 pm 01:37 PM

JavaScript で HTTP ステータス コードを取得する方法の紹介: フロントエンド開発では、バックエンド インターフェイスとの対話を処理する必要があることが多く、HTTP ステータス コードはその非常に重要な部分です。 HTTP ステータス コードを理解して取得すると、インターフェイスから返されたデータをより適切に処理できるようになります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法と、具体的なコード例を紹介します。 1. HTTP ステータス コードとは何ですか? HTTP ステータス コードとは、ブラウザがサーバーへのリクエストを開始したときに、サービスが

Python 非同期プログラミング: 非同期コードで効率的な同時実行性を実現する方法 Python 非同期プログラミング: 非同期コードで効率的な同時実行性を実現する方法 Feb 26, 2024 am 10:00 AM

1. 非同期プログラミングを使用する理由は何ですか?従来のプログラミングではブロッキング I/O が使用されます。つまり、プログラムは操作が完了するまで待機してから続行します。これは単一のタスクではうまく機能する可能性がありますが、多数のタスクを処理する場合にはプログラムの速度が低下する可能性があります。非同期プログラミングは、従来のブロッキング I/O の制限を破り、非ブロッキング I/O を使用します。つまり、プログラムは、タスクの完了を待たずに、タスクを別のスレッドまたはイベント ループに分散して実行できます。これにより、プログラムは複数のタスクを同時に処理できるようになり、プログラムのパフォーマンスと効率が向上します。 2. Python 非同期プログラミングの基礎 Python 非同期プログラミングの基礎は、コルーチンとイベント ループです。コルーチンは、関数の一時停止と再開を切り替えることができる関数です。イベントループはスケジュールを担当します

See all articles