コールバックと約束


Cover Image Credit

コールバック?
// caller
function foo(callback) {
  callback('world');
}

// callback function
function myCallback(name) {
  console.log(`Hello ${name}`); // "hello world"
}

// pass callback to caller
foo(myCallback);
コールバックを理解するには、まず、NodeJSが一般的にコードを実行する方法を理解する必要があります.NodeJsのすべてが“イベントループ”によって制御されているので、それの心nodejsは、単一の、巨大な、非常に複雑なループです.
NODEJSでコードを実行すると、それぞれの行は基本的なV 8(JavaScriptエンジン)で解釈されます.数学や文字列操作などの基本的な言語操作は、結果を呼び出し元に返す.しかし、ネットワーク要求、読み込み、ファイルの書き込みやシステムハードウェアへのアクセスなどの他の操作はすぐに実行されず、代わりにイベントループの“callstack”に追加される.イベントループは常にLIFO(last in first out)順序で利用可能なタスクを実行します.タスクがイベントループを他のイベントループ要素を処理する前に計算を終了するように強制すると、イベントループを「ブロック」するということになります.さらに、完了までブロックするタスクの種類、同期タスクを呼び出します.

This is an amazing article if you want to learn more about the event loop. Seriously, it's great!


イベントループ、非同期タスクに登録できる別の種類のタスクがあります.期待通り、非同期タスクは同期タスクの逆であり、イベントループをブロックしません.代わりに、asyncタスクは、Asyncイベント終了からの結果の処理を処理する「コールバック」機能を提供することが期待されます.これはコールバックが何であるかについて説明します、しかし、なぜ、彼らは必要ですか?

なぜコールバック?
ウェブサイトが1つのブラウザーで彼らの資産1の全てをロードしなければならなくて、全くすべてが取り戻されるまで、レンダリングできないならば、想像してください.その場合は、私のコンピュータに表示するには30秒以上のGmailを取るだろう.コールバックは非常に小さなCPUを消費するタスクを許可することによって問題を解決します.明確に言えば、これは並列性ではありません.なぜなら、2つのことが一度に起こっていないからです.

Source
filesystemなどのコアNODEJS APIのほとんどは、イベントループの最小のブロックを許容するためにasyncを実装しています.それがまだ明確でないならば、私がコールバックを必要とするとき、私が一般化するのを見つけた最善の方法は以下です:
コードが別のシステムと対話し、そのシステムが信頼性(ファイルシステム、ネットワーク、GPU)の信頼性を保証できない場合は、コールバックを必要とします.
たとえば、POSTリクエストをストライプに送信する場合.comは、どのように高速(それはすべての)ストライプを保証することはできません.COMは対応します.この信頼性を処理するには、POSTリクエストを非ブロッキングモードで送信し、ストライプのときに呼び出されるコールバックを登録します.COMサーバーが応答します.そして、そのストライプ.COMリクエストは非同期であるため、AWS S 3サービスに対して並列(並列ではない)リクエストを行い、アプリケーションのロード時間から大きな塊を剃ることができます.

コールバックが悪い理由

Source
時間が経つにつれて人々はコールバックに不満を感じ始めた.理論的コールバックは、延期されたコード実行のための大きい解決です.残念なことに、実際の使用は入れ子になったイベントを処理する深いコールバックネスティングを奨励します

Obviously you don't need callbacks for something like string manipulation. This is just a contrived example to keep things clean and simple.


// caller
function foo(callback) {
  callback('world', myNestedCallback);
}

// inner inner callback
function myNestedNestedCallback(name, callback) {
  console.log(`Hello ${name}`);
  // Prints "Hello First Name: Mr. world"
}

// inner callback
function myNestedCallback(name, callback) {
  callback(`First Name: ${name}`);
}

// callback function
function myCallback(name, callback) {
  callback(`Mr. ${name}`, myNestedNestedCallback);
}

// pass callback to caller
foo(myCallback);
これは「コールバック地獄」として知られています.多くのコールバックの中で入れ子になったときに、混乱するコードがどのようになるのかによって.現在のスコープと利用可能な変数を決定することは、しばしば非常にチャレンジングになります.

Image Source
あなたが複数のものをロードして、彼らが扱われた命令について気にしないとき、コールバックはOKです、しかし、あなたが順序づけられた、連続したコードを書く必要があるとき、彼らは偉大でありません.ほとんどの場合、人々は人工的にシーケンシャルコードとして深いコールバックチェーンを使用しました.イベントループをブロックしなかった解決が必要であったが、極端な入れ子なしでコードを命じられることができました.

約束する
あなたが聞いたことがあっても、Promiseは本当にただのコールバックです.よく定義されたAPIを持つコールバック関数の周りの文字どおりラッパです.Proxy APIを使用すると、基になるASNCイベントの状態を問い合わせることができ、基本的な非同期イベント補完から生成される結果やエラーを処理するロジックを登録できるメソッドがあります.主にネスティング問題を解決します.
// caller
function foo(callback) {
  callback('world', myNestedCallback);
}

// inner inner callback
function myNestedNestedCallback(name, callback) {
  console.log(`Hello ${name}`);
  // Prints "Hello First Name: Mr. world"
}

// inner callback
function myNestedCallback(name, callback) {
  callback(`First Name: ${name}`);
}

// callback function
function myCallback(name, callback) {
  callback(`Mr. ${name}`, myNestedNestedCallback);
}

// pass callback to caller
foo(myCallback);
これを:
function myNestedNestedCallback(name) {
  return new Promise((resolve, reject) => {
    console.log(`Hello ${name}`); // Prints "Hello First Name: Mr. world"
  })
}

function myNestedCallback(name) {
  return new Promise((resolve, reject) => {
    resolve(`First Name: ${name}`);
  });
}


function myCallback(name) {
  return new Promise((resolve, reject) => {
    resolve(`Mr. ${name}`);
  });
}

myCallback('world').then(myNestedCallback).then(myNestedNestedCallback);

コールバックを使用しているコードを約束を使用して等価なコードに変換する場合は、次のようになります.
// callback way
function addCallback(a, b, callback) {
  callback(a + b);
}

// promise way
function addPromise(a, b) {
  return new Promise((resolve, reject) => {
    resolve(a + b);
  });
}
コールバックベースのAPIと相互作用していて、外部からの約束に変換したい場合.
// signature
function makeHTTPRequest(url, method, callback) {}


const convertedToPromise = new Promise((resolve, reject) => {
  makeHTTPRequest('google.com', 'GET', (body, err) => {
    if (err) {
      return reject(err);
    }
    return resolve(body);
  });
});

convertedToPromise.then((res) => console.log(res)); // prints response from google.com
多くのコールバックも、自動的に彼らの「推進された」バージョンに util package in NodeJSを通して変わることができます.
const { promisify } = require('util');

function addCallback(a, b, callback) {
  callback(a + b);
}

const asyncAdd = promisify(addCallback);
asyncAdd(3, 6).then((res) => console.log(res)); // "9"

async待機する
最後に、asyncawaitを持っています.約束とコールバックの関係に似て、asyncawaitは、約束を本当に本当に使う方法です.asyncawaitはネイティブの同期コードのような約束コードを書く構文を提供します.async識別子を関数に使用する場合は、次のプロシージャコードに相当します.
// async version
async function add(a, b) {
  return a + b; // really returns a Promise under the hood
}

// equivalent code but promise way
function addPromise(a, b) {
  return new Promise((resolve, reject) => {
    resolve(a + b);
  });
}

add(1, 2).then((res) => console.log(res)); // "3"
addPromise(1, 2).then((res) => console.log(res)); // "3"
実際には、すべて242479142機能は、本格的な約束オブジェクトを返します.asyncawaitの方法のための追加機能を提供します.Async関数への呼び出し前に待機するときには、明示的なasyncタスクを使用する代わりに、コードの式の左側に直接asyncの結果を返す必要があることを意味します.これにより、同期されたスタイルのコードを書くことができます.それがまだ意味をなさないならば、asyncの等価物が約束にあるものは、ここにあります.
async function add(a, b) {
  return a + b;
}

async function main() {
  const sum = await add(6, 4);
  console.log(sum); // "10" 
}
awaitは、コードをネスティングなしでスタイルを整えることができるawaitのためのちょうどハックです.上記のコードとコードの間には、機能的な違いはありません.
function addPromise(a, b) {
  return new Promise((resolve, reject) => {
    resolve(a + b);
  });
}

addPromise(6, 4).then((res => console.log(res))); // "10"

結論
私は、これがコールバックと約束の後で中心的な力学を理解するのにまだ苦労していた人々を助けたことを望みます.ほとんどの場合、それはすべてのちょうどシンタックス糖質の束であり、本当に複雑ではありません.
あなたがまだそのような基礎的な概念に苦労しているならば.
My blog