非同期から約束へ

1. 背景

1.1、jsシングルスレッド

js はシングルスレッド言語であるため、これらすべては js の誕生の始まりから始まります。

js单线程原因:作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?当然,这可以加锁来解决,但是加锁会变得更复杂,得不偿失。

1.2、同期と非同期の説明

まず、同期と非同期について簡単に理解します。

同步操作可以理解为不花费时间的操作,而异步操作是需要一定时间的。

シングルスレッドとは、地下鉄のゲートを通過するときにキューに並ぶのと同じように、すべてのタスクがキュー内でのみ従順に実行できることを意味します。しかし、カードをスワイプする人、QR コードをスワイプする人、顔をスワイプする人など、費やす時間は異なります。携帯電話がネットワークから外れると、キューに引っかかって輻輳が発生します。これは、単一スレッドによって引き起こされる同期ブロッキングの問題です。

この問題を解決するために、js ではコールバック関数の仕組みが導入されており、ajax などの IO 操作では、非同期リクエストが送信された場合、プログラムはそこでブロックせずに結果が返されるのを待ち、実行を継続します。次のコード。リクエストが正常に結果を取得すると、コールバック関数が呼び出され、次の処理が行われます。これは非同期かつノンブロッキングです。js では、非同期操作の処理は非同期かつノンブロッキングです。

したがって、js はシングルスレッドの非同期プログラミング言語であると言えます。

次のコード:

console.log(1)
setTimeout(()=>{
    
    
    console.log(2)
},100)
console.log(3)
//执行结果:132

setTimeoutで実行されるのは非同期操作のコールバック関数で、完了を待って中のコードが実行されます。これは、インターネットがなかったのに地下鉄の改札口を通過した兄弟に似ています。彼は、最初にネットワークの問題に対処するために脇に立っていましたが、問題を解決した後に改札口に入るために列に並んでいました。アップの人はブロックされません。

1.3、コールバック関数

非同期操作の完了後に結果を使用して何かをしたい場合はどうすればよいでしょうか? 非同期操作の結果処理にコードを書けるという人もいますが、そのようなコードは階層化されており、再利用や保守が困難です。

この問題を解決するために、誰かがコールバック関数スキームを提案しました。

実際、上記の setTimeout のアロー関数はコールバック関数ですが、十分直感的ではありません。別の例を次に示します。

時間のかかる非同期作業の一般的なものは次のとおりです。

定时器
建立网络连接
向文件读写数据

例として、ファイルへのデータの読み取りと書き込みを行うことができます。まず、次のコードを見てください。

const fs = require('fs')
function printA(){
    
    
    console.log(a)
}
function readFile(){
    
    
    fs.readFile('test.txt', (err, data) => {
    
    
        if (err) {
    
    
          console.error(err)
          return
        }
        // data 是二进制类型,需要转换成字符串
        console.log("读取到的文本内容:",data.toString())
        a=1
      })
}
let a=0
readFile()
printA()

上からわかるように、a は変更されますが、ファイルの読み取り操作 (非同期操作) 中に変更されますが、js の非同期およびノンブロッキング機能により、printA で a を印刷するときには実行されません。出力される a は 0 になります。

では、a=1 を出力するにはどうすればよいでしょうか?

関数をパラメータとして readFile に渡し、readFile 関数で印刷操作を実行できます。

const fs = require('fs')
function printA(){
    
    
    console.log(a)
}
function readFile(callback){
    
    
    fs.readFile('test.txt', (err, data) => {
    
    
        if (err) {
    
    
          console.error(err)
          return
        }
        // data 是二进制类型,需要转换成字符串
        console.log("读取到的文本内容:",data.toString())
        a=1
        callback()//这就是传进来的回调函数
      })
}
let a=0
readFile(printA)

これがコールバック関数で、関数本体はある操作が完了した後に内部から外部へ外部関数を呼び出しますが、関数本体内で呼び出されるため、内部から外部へ該当する変数(スコープチェーン)を取得することができます。外。

簡単に言うと、関数をパラメータとして使用し、非同期操作を実行する前にそれを渡します

1.4、jsイベントループ機構

上で述べたように、js の非同期およびノンブロッキング メカニズムでは、1 つのメイン スレッドだけがすべての同期および非同期コードを完全に実行できることをどのようにして実現するのでしょうか?

これには、一般に (イベント ループ) と呼ばれる、js のイベント ループ メカニズムを理解する必要があります。次の図を使用できます。
画像の説明を追加してください

一般的なマイクロタスク:

Promises.(then catch finally),process.nextTick, MutationObserver
DOM渲染前触发

ここで、Promise は同期されており、マイクロタスクは .then の後にあることに注意してください。

一般的なマクロ タスク:

整体代码script,setTimeout,setInterval ,setImmediate,I/O,UI renderingnew 
DOM渲染后触发

つまり、非同期操作が発生した場合、それがマクロタスクであるかマイクロタスクであるかを判断する必要があり、マクロタスクの場合は非同期操作の結果がマクロタスクキューに追加され、マイクロタスクの場合は非同期操作の結果がキューに追加されます。タスクが実行されると、マイクロタスクキューに追加されます。
したがって、非同期キューは元のイベント キューから 2 つのマクロ キューとマイクロ キューに変更され、メインスレッドが空の場合は、最初にマイクロ キュー内を検索します (このプロセスでは、マイクロ キューによって生成された新しいマイクロタスクが実行されます)。イベントはキューの最後尾に追加され、このサイクルでも処理されます。1 つのタスクのみが実行され、それがマイクロタスク キューの番になります)。

第一步: 主线程执行同步任务的同时,把一些异步任务放入‘任务队列’(task queue)中,等待主线程的调用栈为空时,再依次从队列出去任务去执行;
第二步:检测任务队列中的微队列是否为空,若不为空,则取出一个微任务入栈执行;然后继续执行第2步;如果微队列为空,则开始取出宏队列中的一个宏任务执行;
第三步:执行完宏队列中的一个宏任务后,会继续检测微队列是否为空,如果有新插入的任务,这继续执行第二步;如果微队列为空,则继续执行宏队列中的下一个任务,然后再继续循环执行第三步;

例:

const fs = require('fs')
setTimeout(function callback(){
    
    
	console.log('2')//宏任务
    Promise.resolve().then(()=>{
    
    
        console.log(6)//宏任务中微任务
    })
}, 0)
setTimeout(function callback(){
    
    
	console.log('又一个宏任务')//宏任务
}, 0)
new Promise((resolve, reject) => {
    
    
    console.log('3')//同步
    resolve()
})
.then(res => {
    
    
    console.log('4');//微任务
    new Promise((resolve, reject) => {
    
    
        console.log('8')//同步
        fs.readF ile('test.txt', (err, data) => {
    
    
            if (err) {
    
    
              console.error(err)
              return
            }
            // data 是二进制类型,需要转换成字符串
            console.log("读取到的文本内容:",data.toString())
            resolve(data.toString())//宏任务
          })
    })
    .then(res => {
    
    
        new Promise((resolve, reject) => {
    
    
            console.log('测试')//同步
            resolve()
        })
        .then(res => {
    
    
            console.log('再测试');//微任务
        })
    })
})
console.log('5')//同步

印刷値:

3
5
4
8
2
6
又一个宏任务
读取到的文本内容: 测试文本
测试
再测试

1.5、コールバック関数の問題とPromiseの誕生

それ以来、js はイベント ループ メカニズムに依存してすべてのタスクを実行できるようになりました。ただし、一部のタスクは非同期タスクによって返された結果に依存する必要があるため、複数の非同期操作があり、最終的な操作がこれらのいくつかの非同期操作に依存する場合は、コールバック関数ソリューションを採用しました。そうなると、レイヤーごとにネストするのが簡単になり、コールバック地獄の問題が発生します。

次のコードでは、3 つの txt ファイルを作成し、ファイルの内容を 1 つずつ読み取って出力する必要があります。コールバック関数を記述すると、次のようになります。

const fs = require('fs')
fs.readFile('test.txt', (err,data) => {
    
    
    let reader1=data.toString()
    fs.readFile('test1.txt', (err,data) => {
    
    
        let reader2=data.toString()
        fs.readFile('test2.txt', (err,data) => {
    
    
            let reader3=data.toString()
            setTimeout(()=>{
    
    
                let result=reader1+reader1+reader3
                console.log("获得的结果",result)
            },10)
        })
    })
})

fs モジュールの readFile の第 2 パラメータはコールバック関数です テキスト内容を順番に読み込むためには複数層のネストが必要です これは数回だけですが、実際のプロジェクトでは簡単に複数層にすることができますその結果、コールバック地獄が発生し、読み取りと保守が困難になります。

この問題を解決するためにpromiseが誕生しました。

コールバック地獄が発生する理由は、コールバック関数による結果の処理と非同期操作が結局一緒であり、分離されていないことにあります。Promise導入の最大の機能は非同期操作の処理と結果を分離することであり、promise.then()を利用して非同期操作の結果取得と処理が可能になります。Promise を使用するように上記のコードを変更します。

const fs = require('fs')
function readFile(fileName){
    
    
    return new Promise((resolve,reject)=>{
    
    
        fs.readFile(fileName, (err,data) => {
    
    
            resolve(data.toString())
        })
    })
}
let res1,res2,res3
readFile('test.txt')
.then((res)=>{
    
    
    res1=res
    return readFile('test1.txt')
})
.then((res)=>{
    
    
    res2=res
    return readFile('test2.txt')
})
.then((res)=>{
    
    
    res3=res
    console.log("结果",res1+res2+res3)
})

このように、非同期操作と結果は .then によって分離されます。コールバック地獄を避けてください。

次に、promise の then メソッドの実装

では、約束とは一体何でしょうか?なぜそのような効果が得られるのでしょうか?

2.1、約束の 3 つの状態

pending: 一个promise在resolve或者reject前就处于这个状态。
fulfilled: 一个promise被resolve后就处于fulfilled状态,这个状态不能再改变,而且必须拥有一个不可变的值(value)rejected: 一个promise被reject后就处于rejected状态,这个状态也不能再改变,而且必须拥有一个不可变的拒绝原因(reason)

2.2,promise.then方法

Promise には、thenその値または拒否の理由にアクセスするメソッドが必要です。thenこのメソッドは 2 つのパラメータを取ります。

promise.then(onFulfilled, onRejected)

これら 2 つのパラメータには次の特性があります。

1,onFulfilled 和 onRejected 都是可选参数。若不存在则忽略。
2,onFulfilled在promise结束前不可调用,结束后必须被调用,其第一个参数为 promise 的终值value,且调用次数不超过一次。
3,onRejected在被拒绝前不可被调用,拒绝执行后其必须被调用,其第一个参数为 promise 的原因reason,且调用册数不超过一次。

そして、この then メソッドは次の機能もサポートしています。

1,then 方法必须返回一个 promise 对象。这样才支持多次then链式调用。
2,then 方法可以被同一个 promise 调用多次。当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调,当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调。

2.3、まず非同期操作と結果の分離を実現するための基本的な約束を書きます

通常、promise は次のように使用します。

const p1 = new Promise((resolve, reject) => {
    
    
    console.log('create a promise');
    setTimeout(()=>{
    
    
        console.log("异步")
        resolve('成功了');
    },10)
    
})
const p2 = p1.then((res)=>{
    
    
	console.log(res)
})

そのため、promise は、resolve と accept という 2 つのパラメーターを持つ関数を受け入れ、この関数を同期的に実行します。

// 先定义三个常量表示状态
var PENDING = 'pending';//等待中
var FULFILLED = 'fulfilled';//执行完成
var REJECTED = 'rejected';//拒绝执行

function MyPromise(fn) {
    
    
    this.status = PENDING;    // 初始状态为pending
    this.value = null;        // 初始化value
    this.reason = null;       // 初始化reason
    //同步执行这个函数
    try {
    
    
        fn(resolve, reject);
    } catch (error) {
    
    
        reject(error);
    }
}
exports.MyPromise=MyPromise

fn(resolve, request) 非同期操作が実行された後、resolve と拒否が呼び出されることがわかります。そのため、呼び出しを提供するには、promise 内で解決と拒否を作成する必要があります。

// 这两个方法直接写在构造函数里面
function MyPromise(fn) {
    
    
  // ...省略前面代码...
  
  // 存一下this,以便resolve和reject里面访问
  var that = this;
  // resolve方法参数是value
  function resolve(value) {
    
    
    if(that.status === PENDING) {
    
    
      that.status = FULFILLED;
      that.value = value;
    }
  }
  
  // reject方法参数是reason
  function reject(reason) {
    
    
    if(that.status === PENDING) {
    
    
      that.status = REJECTED;
      that.reason = reason;
    }
  }
}

このようにして、Promise によってラップされた非同期操作が実行された後、resolve または Reject を呼び出して Promise の状態を変更し、結果の値または理由を取得できます。

ただし、現在のコードに関する限り、結果を取得して処理するために then メソッドも必要です。

以前の分析によると、thenメソッドはチェーン内で呼び出すことができるため、インスタンス メソッドであり、仕様の API は ですpromise.then(onFulfilled, onRejected)。最初にシェルフを設定します。

MyPromise.prototype.then = function(onFulfilled, onRejected) {}

このようにして、Promise 内で非同期操作を実行します。非同期操作が完了すると、resolve メソッドが呼び出され、Promise の状態が変更され、その結果が Promise の値に格納されます。では、この結果を取得して then メソッドで実行するにはどうすればよいでしょうか?

まず、非同期操作が実行された(Promiseの状態がFULFILLEDになった)ことを判断する必要があります。このときpromiseの値には非同期操作の結果が格納されているので、thenのパラメータは関数にするだけで値を渡すことができます。

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    
    
    if(this.status === FULFILLED) {
    
    
        onFulfilled(this.value)//将结果传入回调函数
      }
    
    if(this.status === REJECTED) {
    
    
        onRejected(this.reason);
    }
}

今書いているプロミスを試して、次のコードを書いてみましょう。

var MyPromise = require('./test.js').MyPromise;
const p1 = new MyPromise((resolve, reject) => {
    
    
    console.log('create a promise');
    setTimeout(()=>{
    
    
        console.log("异步")
        resolve('成功了');
    },10)
    
  })
console.log("直接打印",p1)
setTimeout(()=>{
    
    
    console.log("过两s后",p1)
    const aaa=p1.then((value)=>{
    
    
        console.log(value)
    })
},2000)

印刷結果:

画像の説明を追加してください

ここでは setTimeout を使用して then 関数をラップしていることに注意してください。つまり、結果は処理のために then にフェッチされるだけであり、then でコードを実行する前に非同期の完了を待つことはできません。

この時点で、promise が何であるかは大体わかりますね。

これは、非同期操作の処理サイトとして考えてください。

初期状態は保留状態ですが、非同期操作(ユーザー定義)が実行されると、resolve/rejectが呼び出され、変更された状態がfulfilled/rejectedとなり、結果がvalue/reasonに格納されます。

次に、メソッドは非同期操作が完了したかどうかを判断し、完了した場合は値/理由を使用してユーザーの次の操作 (ユーザー定義) を完了します。

実際、コールバック関数は依然として使用されており、promsie は非同期操作と結果を分離するための転送ステーションとして使用されています。(非同期操作は Promise の入力パラメーター関数に配置され、結果は value/reason に配置されます。ここで結果を取得する前に then メソッドを使用する必要があります)。

画像の説明を追加してください

2.4、thenメソッドのコールバック関数の収集と実行

実際、Promise を使用するときは、上記のように setTimeout でラップしませんが、より多くの場合、次のように使用します。

new Promise(fn).then(onFulfilled, onRejected);

上記のコードは、thenインスタンス オブジェクトが作成されるとすぐに呼び出されます。この時点では、fn内部の非同期操作は完了していない可能性があります。つまり、まだそこにstatusありますPENDING。このとき、then 内のコードは実行してはなりません。結果が値に格納されていないためです。

では、非同期操作が完了した後、then でコードを実行するにはどうすればよいでしょうか? 前述したように、resolve メソッドと拒否メソッドは非同期操作の完了後に呼び出されるため、それまでに渡されたコールバック関数をここで実行できます。

そこで、約束を変更します。

// 构造函数
function MyPromise(fn) {
    
    
  // ...省略其他代码...
  
  // 构造函数里面添加两个数组存储成功和失败的回调
  this.onFulfilledCallbacks = [];
  this.onRejectedCallbacks = [];
  
  function resolve(value) {
    
    
    if(that.status === PENDING) {
    
    
      // ...省略其他代码...
      // resolve里面将所有成功的回调拿出来执行
      that.onFulfilledCallbacks.forEach(callback => {
    
    
        callback(that.value);
      });
    }
  }
  
  function reject(reason) {
    
    
    if(that.status === PENDING) {
    
    
      // ...省略其他代码...
      // resolve里面将所有失败的回调拿出来执行
      that.onRejectedCallbacks.forEach(callback => {
    
    
        callback(that.reason);
      });
    }
  }
}

// then方法
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    
    
  // ...省略其他代码...
  // 如果还是PENDING状态,将回调保存下来
  if(this.status === PENDING) {
    
    
    this.onFulfilledCallbacks.push(onFulfilled);
    this.onRejectedCallbacks.push(onRejected);
  }
}

つまり、promise.then が行うことは、受信したコールバック関数を収集し、収集したコールバック関数を解決または拒否で取り出して実行することです。

したがって。これにより、非同期操作の終了後に then のコールバック関数が確実に実行されるようになります。これまでのところ、コアの非同期操作プロセスと約束の結果の分離を最初に実現しました。

画像の説明を追加してください

2.5、then の入力と戻り

2.5.1、そのときの入力パラメータ

前に述べたように、then メソッドの入力パラメータが関数でない場合、それは無視されます。実際、いわゆる「無視」は何もしないことを意味するのではなく、「無視」はそのまま返すことを意味しonFulfilledますvalue。この場合、エラー ブランチであるためonRejectedを返すことになり、スローされるはずの Error を返すため、ここでこれら 2 つの入力パラメータを書き換える必要があります。reasononRejectedreason

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    
    
    // 如果onFulfilled不是函数,给一个默认函数,返回value
    var realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value

    // 如果onRejected不是函数,给一个默认函数,返回reason的Error
	var realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{
    
    throw reason}
	//...省略其他代码
}
2.5.2、then の返却は約束でなければなりません

thenの戻り値は Promise である必要があります (ここでは Promise2 が使用されています)。そして、onFulfilled または onRejected が例外 e をスローした場合、この新しい Promise2 は拒否して理由 e を返さなければなりません。つまり、onFulfilled と onRejected を try...catch でラップします。

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    
    
   // 如果onFulfilled不是函数,给一个默认函数,返回value
   var realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value

  // // 如果onRejected不是函数,给一个默认函数,返回reason的Error
	var realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{
    
    throw reason}

  var that=this
  var promise2 = new MyPromise(function(resolve, reject) {
    
    
        //如果异步已经成功则直接执行回调函数,期间有报错,需要reject
    if(that.status === FULFILLED) {
    
    
        try {
    
    
            realOnFulfilled(that.value);
        } catch (error) {
    
    
            reject(error);
        } 
    }
      //如果异步已经失败则直接执行回调函数,期间有报错,需要reject
    if(that.status === REJECTED) {
    
     
        try {
    
    
            realOnRejected(that.reason);
        } catch (error) {
    
    
            reject(error);
        }
    }
      // 如果还是PENDING状态,将回调保存下来,为了捕获回调中的错误,需要try...catch包裹一层
    if(that.status === PENDING) {
    
    
        that.onFulfilledCallbacks.push(function() {
    
    
            try {
    
    
              realOnFulfilled(that.value);
            } catch (error) {
    
    
              reject(error);
            }
        });
        that.onRejectedCallbacks.push(function() {
    
    
            try {
    
    
              realOnRejected(that.reason);
            } catch (error) {
    
    
              reject(error);
            }
        });
      }
    })
  return promise2
}
2.5.3、then の入力パラメータはデフォルトで関数です

onFulfilled が関数ではなく、promise1 が成功した場合、promise2 も成功して同じ値を返す必要があります。このとき、promise2に対してresolve(this.value)を透過的に送信する必要がある。

が関数onRejectedではなくpromise1実行を拒否する場合は、promise2実行を拒否して同じ理由を返す必要があります。

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    
    
    // 如果onFulfilled不是函数,给一个默认函数,返回value
    var realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value

    // // 如果onRejected不是函数,给一个默认函数,返回reason的Error
	var realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{
    
    throw reason}

    var that=this
    //如果异步已经成功则直接执行回调函数,期间有报错,需要reject
    if(this.status === FULFILLED) {
    
    
        var promise2 = new MyPromise(function(resolve, reject) {
    
    
            try {
    
    
                //then方法传入非函数,且成功执行,则把结果传给promise2
                if (typeof onFulfilled !== 'function') {
    
    
                    resolve(that.value);
                } else {
    
    
                    realOnFulfilled(that.value);
                    resolve(that.value);
                }
            } catch (error) {
    
    
                reject(error);
            } 
        });
        return promise2;
    }
    //如果异步已经失败则直接执行回调函数,期间有报错,需要reject
    if(this.status === REJECTED) {
    
            
        var promise2 = new MyPromise(function(resolve, reject) {
    
    
            try {
    
    
                //then方法传入非函数,且成功执行,则把结果传给promise2
                if (typeof onRejected !== 'function') {
    
    
                    reject(that.reason);
                } else {
    
    
                    realOnRejected(that.reason);
                    //promise1的onRejected执行成功后,promise2应该被resolve,这样promise2的状态才会是fulfilled
                    resolve();
                }
            } catch (error) {
    
    
                reject(error);
            }
        });
        return promise2;
    }
    // 如果还是PENDING状态,将回调保存下来,为了捕获回调中的错误,需要try...catch包裹一层
    if(this.status === PENDING) {
    
    
        var promise2 = new MyPromise(function(resolve, reject) {
    
    
            that.onFulfilledCallbacks.push(function() {
    
    
              try {
    
    
                  //如果传入的不是函数,则透传结果给promise2
                    if (typeof onFulfilled !== 'function') {
    
    
                        resolve(that.value);
                    } else {
    
    
                        realOnFulfilled(that.value);
                        resolve(that.value);
                    }
              } catch (error) {
    
    
                reject(error);
              }
            });
            that.onRejectedCallbacks.push(function() {
    
    
              try {
    
    
                  //如果传入的不是函数,则透传结果给promise2
                if (typeof onRejected !== 'function') {
    
    
                    reject(that.reason);
                } else {
    
    
                    realOnRejected(that.reason);
                    resolve()
                }
              } catch (error) {
    
    
                reject(error);
              }
            });
        });
        return promise2;
    }

}
2.5.4、then に渡されたコールバック関数に戻り値がある場合

onFulfilledor がonRejected値を返しx、x のタイプを区別する必要がある場合は、次のPromise 解決プロセスを実行します。

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    
    
  // 如果onFulfilled不是函数,给一个默认函数,返回value
  var realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value

  // // 如果onRejected不是函数,给一个默认函数,返回reason的Error
	var realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{
    
    throw reason}

  var that=this
  var promise2 = new MyPromise(function(resolve, reject) {
    
    
        //如果异步已经成功则直接执行回调函数,期间有报错,需要reject
    if(that.status === FULFILLED) {
    
    
        try {
    
    
          if (typeof onFulfilled !== 'function') {
    
    
            resolve(that.value);
          } else {
    
    
            var x = realOnFulfilled(that.value);
            resolvePromise(promise2, x, resolve, reject);
          }
        } catch (error) {
    
    
            reject(error);
        } 
    }
      //如果异步已经失败则直接执行回调函数,期间有报错,需要reject
    if(that.status === REJECTED) {
    
     
        try {
    
    
          if (typeof onRejected !== 'function') {
    
    
            reject(that.reason);
          } else {
    
    
            var x = realOnRejected(that.reason);
            resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程
          }
        } catch (error) {
    
    
            reject(error);
        }
    }
      // 如果还是PENDING状态,将回调保存下来,为了捕获回调中的错误,需要try...catch包裹一层
    if(that.status === PENDING) {
    
    
        that.onFulfilledCallbacks.push(function() {
    
    
            try {
    
    
              if (typeof onFulfilled !== 'function') {
    
    
                resolve(that.value);
              } else {
    
    
                var x = realOnFulfilled(that.value);
                resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程
              }
            } catch (error) {
    
    
              reject(error);
            }
        });
        that.onRejectedCallbacks.push(function() {
    
    
            try {
    
    
              if (typeof onRejected !== 'function') {
    
    
                reject(that.reason);
              } else {
    
    
                var x = realOnRejected(that.reason);
                resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程
              }
            } catch (error) {
    
    
              reject(error);
            }
        });
      }
    })
  return promise2
}

仕様にはもう 1 つの項目があります。これはonFulfilled、実行環境スタックにプラットフォーム コードのみが含まれている場合にのみ呼び出すことができます。この記事は、実際には非同期に実行されるようにする必要があり、メソッド。したがって、実行するときにを含める必要があります。onRejectedonFulfilledonRejectedthenonFulfilledonRejectedsetTimeout

resolvePromise(promise2, x, resolve, reject);私の個人的な理解によれば、ここで setTimeout をラップする目的は、ここで渡される Promise2 が未定義でないようにすることです。

したがって、 then メソッドは次のようになります。

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    
    
  // 如果onFulfilled不是函数,给一个默认函数,返回value
  var realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value

  // // 如果onRejected不是函数,给一个默认函数,返回reason的Error
	var realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{
    
    throw reason}

  var that=this
  var promise2 = new MyPromise(function(resolve, reject) {
    
    
        //如果异步已经成功则直接执行回调函数,期间有报错,需要reject
    if(that.status === FULFILLED) {
    
    
      setTimeout(function() {
    
    
        try {
    
    
          if (typeof onFulfilled !== 'function') {
    
    
            resolve(that.value);
          } else {
    
    
            var x = realOnFulfilled(that.value);
            resolvePromise(promise2, x, resolve, reject);
          }
        } catch (error) {
    
    
            reject(error);
        } 
      },0)
    }
      //如果异步已经失败则直接执行回调函数,期间有报错,需要reject
    if(that.status === REJECTED) {
    
      
      setTimeout(function() {
    
    
        try {
    
    
          if (typeof onRejected !== 'function') {
    
    
            reject(that.reason);
          } else {
    
    
            var x = realOnRejected(that.reason);
            resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程
          }
        } catch (error) {
    
    
            reject(error);
        }
      },0)
    }
      // 如果还是PENDING状态,将回调保存下来,为了捕获回调中的错误,需要try...catch包裹一层
    if(that.status === PENDING) {
    
    
        that.onFulfilledCallbacks.push(function() {
    
    
          setTimeout(function () {
    
    
            try {
    
    
              if (typeof onFulfilled !== 'function') {
    
    
                resolve(that.value);
              } else {
    
    
                var x = realOnFulfilled(that.value);
                resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程
              }
            } catch (error) {
    
    
              reject(error);
            }
          },0)
        });
        that.onRejectedCallbacks.push(function() {
    
    
          setTimeout(function () {
    
    
            try {
    
    
              if (typeof onRejected !== 'function') {
    
    
                reject(that.reason);
              } else {
    
    
                var x = realOnRejected(that.reason);
                resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程
              }
            } catch (error) {
    
    
              reject(error);
            }
          },0)
        });
      }
    })
  return promise2
}

2.6、約束の解決プロセス

以上より、Promiseの解決処理は、それまでに渡された関数に戻り値がある場合に対処することになります。したがって、戻り値が異なれば、異なる処理スキームが必要になります。実装の原理は1点で、thenメソッドが返すpromise2の値にthen入力関数の戻り値を受け取るようにする。

したがって、resolvePromise は次のように記述されます。

resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程

promise2 を渡し、 then input 関数の return x (おそらくそうではない) を渡し、次にpromise2 のresolveとrejectを渡し、それを使用してpromise2の状態と値を変更します。

2.6.1、promiseとxが同じオブジェクトを参照している場合、TypeErrorを理由としてpromiseを拒否(拒否)します。
function resolvePromise(promise, x, resolve, reject) {
    // 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
    // 这是为了防止死循环
    if (promise === x) {
      return reject(new TypeError('The promise and the return value are the same'));
    }
}
2.6.2、then input 関数が Promise を返す場合

x が Promise の場合、返された Promise をレイヤーごとに再帰的に展開して結果 y を取得し、promise2 とその解決および拒否を渡します。この方法でのみ、promise2 は最終結果を取得できます。

function resolvePromise(promise, x, resolve, reject) {
    
    
    // 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
    // 这是为了防止死循环
    if (promise === x) {
    
    
      return reject(new TypeError('The promise and the return value are the same'));
    }
  
    if (x instanceof MyPromise) {
    
    
      // 如果 x 为 Promise ,则让then返回的 promise 接受 x 的状态和结果
      // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
      x.then(function (y) {
    
    
        //注意这里传入的就是入参promise,也就是修改这个promise的状态和结果,简单写就是 resolve(y)
        resolvePromise(promise, y, resolve, reject);
      }, reject);
    }
}
2.6.3 で、x がオブジェクトまたはメソッドの場合、ここで扱われるのは Promise のようなオブジェクト、つまり then メソッドを持つオブジェクトです。
2.3.3另外,如果x是个对象或者方法,这里处理的是类promise对象,即具备then方法的对象。
    2.3.3.1 让x作为x.then
    2.3.3.2 如果取回的x.then属性的结果为一个异常e,用e作为原因reject promise
    2.3.3.3 如果then是一个方法,把x当作this来调用它, 第一个参数为 resolvePromise,第二个参数为rejectPromise,其中:
        2.3.3.3.1 如果/当 resolvePromise被一个值y调用,运行 [[Resolve]](promise, y)
        2.3.3.3.2 如果/当 rejectPromise被一个原因r调用,用r拒绝(reject)promise
        2.3.3.3.3 如果resolvePromise和 rejectPromise都被调用,或者对同一个参数进行多次调用,第一次调用执行,任何进一步的调用都被忽略
        2.3.3.3.4 如果调用then抛出一个异常e,
            2.3.3.3.4.1 如果resolvePromise或 rejectPromise已被调用,忽略。
            2.3.3.3.4.2 或者, 用e作为reason拒绝(reject)promise
	2.3.3.4 如果then不是一个函数,用x完成(fulfill)promise

ここでの仕様は非常に複雑で、実際には、返された promsie (thenable) オブジェクトを処理するためのものです。

次のコード:

var thenableA = {
    
    
    name: 'thenableA',
    then: function (resolve, reject) {
    
    
        console.log(`I'am ${
      
      this.name}`);
        resolve(this.name)
    }
}
var p1=new MyPromise((resolve, reject) => {
    
    
    console.log('create promise1');
    resolve(1)
})
p1.then((res) => {
    
    
    return thenableA
})

印刷出力は次のとおりです。

create promise1
I'am thenableA

then メソッドの具体的な実装は、promise のように thenable オブジェクトを展開し、その中で then メソッドを実行し、then メソッドによって返される Promise2 の値に最終値を受け取るようにすることです。

function resolvePromise(promise, x, resolve, reject) {
    
    
    //...其他代码
    // 如果 x 为对象或者函数,即可能是thenable的对象/函数
    else if (x  &&(typeof x === 'object' || typeof x === 'function')) {
    
    
      var called = false;
      try {
    
    
        // 把 x.then 赋值给 then 
        var then = x.then;
        // 如果 then 是函数,则返回的其实是类promise(thenable)对象,需要展开执行其中的then方法
        if (typeof then === 'function') {
    
    
            then.call(
              x,
              function (y) {
    
    
                if (called) return;
                called = true;
                resolvePromise(promise, y, resolve, reject);//把它的then方法执行掉,并且修改promise2的值和状态
              },
              function (r) {
    
    
                if (called) return;
                called = true;
                reject(r);
              });
        } else {
    
    
          // 如果 then 不是函数(then返回的不是thenable对象),以 x 为参数执行 promise
          if (called) return;
          called = true;
          resolve(x);
        }
      } catch (error) {
    
    
        // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise,return是为了不执行后续的代码
        if (called) return;
        called = true;
        return reject(error);
      }
}

たとえば、上記のコード:

var thenableA = {
    
    
    name: 'thenableA',
    then: function (resolve, reject) {
    
    
        console.log(`I'am ${
      
      this.name}`);
        resolve(this.name)
    }
}
var p1=new MyPromise((resolve, reject) => {
    
    
    console.log('create promise1');
    resolve(1)
})
var p2=p1.then((res) => {
    
    
    return thenableA
})

setTimeout(()=>{
    
    
    console.log("+++++",p2)
},0)

実行後に出力される内容は次のとおりです。

create promise1
I'am thenableA
+++++ MyPromise {
    
    
  status: 'fulfilled',
  value: 'thenableA',
  reason: null,
  onFulfilledCallbacks: [],
  onRejectedCallbacks: []
}

thenableA の then メソッドが実行され、promise2 の値が変更されるため、p2 の値が「thenableA」になっていることがわかります。

2.6.4、返された x は関数でもオブジェクトでもありません、さらに返されません (x は未定義です)

x がオブジェクトでも関数でもない場合、x を使用して約束を実行します。つまり、resolve(x) を直接実行します。

3. PromiseA+ の仕様を確認する

作成した MyPromsie に次のコードを追加します。

MyPromise.deferred = function() {
    
    
  var result = {
    
    };
  result.promise = new MyPromise(function(resolve, reject){
    
    
    result.resolve = resolve;
    result.reject = reject;
  });

  return result;
}
module.exports = MyPromise//记得这一步必须这样导出,不然会报错。

次に、プロジェクト ディレクトリにテスト ライブラリをインストールします。

npm i promises-aplus-tests -g

したがって、私たちの手書きの約束の完全な内容は次のとおりです。


// 先定义三个常量表示状态
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';

function MyPromise(fn) {
    
    
    this.status = PENDING;    // 初始状态为pending
    this.value = null;        // 初始化value
    this.reason = null;       // 初始化reason
    // 构造函数里面添加两个数组存储成功和失败的回调
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    // 存一下this,以便resolve和reject里面访问
    var that = this;
    // resolve方法参数是value
    function resolve(value) {
    
    
        if(that.status === PENDING) {
    
    
            that.status = FULFILLED;
            that.value = value;
            // resolve里面将所有成功的回调拿出来执行
            that.onFulfilledCallbacks.forEach(callback => {
    
    
                callback(that.value);
            });
        }
    }

    // reject方法参数是reason
    function reject(reason) {
    
    
        if(that.status === PENDING) {
    
    
            that.status = REJECTED;
            that.reason = reason;
            // resolve里面将所有失败的回调拿出来执行
            that.onRejectedCallbacks.forEach(callback => {
    
    
                callback(that.reason);
            });
        }
    }

    try {
    
    
        fn(resolve, reject);
    } catch (error) {
    
    
        reject(error);
    }
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    
    
  // 如果onFulfilled不是函数,给一个默认函数,返回value
  var realOnFulfilled=typeof onFulfilled === 'function' ? onFulfilled : value=>value

  // // 如果onRejected不是函数,给一个默认函数,返回reason的Error
	var realOnRejected =typeof onRejected === 'function' ? onRejected : reason=>{
    
    throw reason}

  var that=this
  var promise2 = new MyPromise(function(resolve, reject) {
    
    
        //如果异步已经成功则直接执行回调函数,期间有报错,需要reject
    if(that.status === FULFILLED) {
    
    
      setTimeout(function() {
    
    
        try {
    
    
          if (typeof onFulfilled !== 'function') {
    
    
            resolve(that.value);
          } else {
    
    
            var x = realOnFulfilled(that.value);
            resolvePromise(promise2, x, resolve, reject);
          }
        } catch (error) {
    
    
            reject(error);
        } 
      },0)
    }
      //如果异步已经失败则直接执行回调函数,期间有报错,需要reject
    if(that.status === REJECTED) {
    
      
      setTimeout(function() {
    
    
        try {
    
    
          if (typeof onRejected !== 'function') {
    
    
            reject(that.reason);
          } else {
    
    
            var x = realOnRejected(that.reason);
            resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程
          }
        } catch (error) {
    
    
            reject(error);
        }
      },0)
    }
      // 如果还是PENDING状态,将回调保存下来,为了捕获回调中的错误,需要try...catch包裹一层
    if(that.status === PENDING) {
    
    
        that.onFulfilledCallbacks.push(function() {
    
    
          setTimeout(function () {
    
    
            try {
    
    
              if (typeof onFulfilled !== 'function') {
    
    
                resolve(that.value);
              } else {
    
    
                var x = realOnFulfilled(that.value);
                resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程
              }
            } catch (error) {
    
    
              reject(error);
            }
          },0)
        });
        that.onRejectedCallbacks.push(function() {
    
    
          setTimeout(function () {
    
    
            try {
    
    
              if (typeof onRejected !== 'function') {
    
    
                reject(that.reason);
              } else {
    
    
                var x = realOnRejected(that.reason);
                resolvePromise(promise2, x, resolve, reject);   // 调用Promise 解决过程
              }
            } catch (error) {
    
    
              reject(error);
            }
          },0)
        });
      }
    })
  return promise2
}

function resolvePromise(promise, x, resolve, reject) {
    
    
    // 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
    // 这是为了防止死循环
    if (promise === x) {
    
    
      return reject(new TypeError('The promise and the return value are the same'));
    }
  
    if (x instanceof MyPromise) {
    
    
      console.log("是MyPromise")
      // 如果 x 为 Promise ,则让then返回的 promise 接受 x 的状态和结果
      // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
      x.then(function (y) {
    
    
        //注意这里传入的就是入参promise,也就是修改这个promise的状态和结果,简单写就是 resolve(y)
        resolvePromise(promise, y, resolve, reject);
      }, reject);
    }
    // 如果 x 为对象或者函数
    else if (x  &&(typeof x === 'object' || typeof x === 'function')) {
    
    
      var called = false;
      try {
    
    
        // 把 x.then 赋值给 then 
        var then = x.then;
        // 如果 then 是函数,则返回的其实是类promise(thenable)对象,需要展开执行其中的then方法
        if (typeof then === 'function') {
    
    
            then.call(
              x,
              function (y) {
    
    
                if (called) return;
                called = true;
                resolvePromise(promise, y, resolve, reject);
              },
              function (r) {
    
    
                if (called) return;
                called = true;
                reject(r);
              });
        } else {
    
    
          // 如果 then 不是函数(then返回的不是thenable对象),以 x 为参数执行 promise
          if (called) return;
          called = true;
          resolve(x);
        }
      } catch (error) {
    
    
        // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise,return是为了不执行后续的代码
        if (called) return;
        called = true;
        return reject(error);
      }
    } else {
    
    
      // 如果 x 不为对象或者函数,以 x 为参数执行 promise,没有返回时也执行这个
      resolve(x);
    }
}

MyPromise.deferred = function() {
    
    
  var result = {
    
    };
  result.promise = new MyPromise(function(resolve, reject){
    
    
    result.resolve = resolve;
    result.reject = reject;
  });

  return result;
}
module.exports = MyPromise

プロジェクトのルート ディレクトリでテストが実行されます。

promises-aplus-tests test.js(test.js是我的文件名)

すべてのテスト ケースが合格したことがわかります。

画像の説明を追加してください

4 番目に、promise.resolve の実装

その機能は、既存のオブジェクトを Promise オブジェクトに変換することです。

つまり、Promise の場合は直接戻り、そうでない場合は戻り値がこの Promise になります。

MyPromise.resolve = function(parameter) {
    
    
  if(parameter instanceof MyPromise) {
    
    
    return parameter;
  }

  return new MyPromise(function(resolve) {
    
    
    resolve(parameter);
  });
}

たった今:

var promise1 = new MyPromise((resolve, reject) => {
    
    
    setTimeout(()=>{
    
    
        resolve(2)
    },10)
})
var p2=MyPromise.resolve(promise1)
p2.then((res)=>{
    
    
    console.log(res)//打印2
})

5、Promise.rejectの実装

ステータスが拒否された新しい Promise インスタンスを返します。Promise.reject メソッドのパラメーターreason は、インスタンスのコールバック関数に渡されます。

MyPromise.reject = function(reason) {
    
    
  return new MyPromise(function(resolve, reject) {
    
    
    reject(reason);
  });
}

六、Promise.allの実装

このメソッドは、複数の Promise インスタンスを新しい Promise インスタンスにラップするために使用されます。配列をパラメータとして受け入れます。 、p1p2および はp3すべて Promise インスタンスです。そうでない場合は、Promise.resolveメソッドが最初に呼び出され、パラメータはその後の処理の前に Promise インスタンスに変換されます。p1、p2、および p3 がすべて解決されると、外側の Promise が解決され、拒否がある場合は外側の Promise が拒否されます。

const p = Promise.all([p1, p2, p3]);
MyPromise.all = function(promiseList) {
    
    
  var resPromise = new MyPromise(function(resolve, reject) {
    
    
    var count = 0;
    var result = [];
    var length = promiseList.length;

    if(length === 0) {
    
    
      return resolve(result);
    }

    promiseList.forEach(function(promise, index) {
    
    
      MyPromise.resolve(promise).then(function(value){
    
    
        count++;
        result[index] = value;
        if(count === length) {
    
    
          //全部执行完毕后才resolve结果数组出去
          resolve(result);
        }
      }, function(reason){
    
    
        reject(reason);
      });
    });
  });

  return resPromise;
}

使用例:

var p1 = new MyPromise((resolve, reject) => {
    
    
    setTimeout(()=>{
    
    
        resolve(1)
    },10)
})
var p2 = new MyPromise((resolve, reject) => {
    
    
    setTimeout(()=>{
    
    
        resolve(2)
    },0)
})
var p3 = new MyPromise((resolve, reject) => {
    
    
    setTimeout(()=>{
    
    
        resolve(3)
    },20)
})

var result=MyPromise.all([p1,p2,p3])
setTimeout(()=>{
    
    
    console.log(result)//打印返回的所有结果
},1000)
result.then((res)=>{
    
    
    console.log(res)
})

得られた結果:

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-Z0tyj5fd-1680622612470) (C:\Users\Administrator\AppData\Roaming\Typora\) typora-user-images\ image-20230329225315698.png)]

7、Promise.raceメソッド

使用法は、Promise の配列を渡す Promise.all と同じですが、Promise.race は、Promise が実行されると結果を返す点が異なります。

MyPromise.race = function(promiseList) {
    
    
  var resPromise = new MyPromise(function(resolve, reject) {
    
    
    var length = promiseList.length;

    if(length === 0) {
    
    
      return resolve();
    } else {
    
    
      for(var i = 0; i < length; i++) {
    
    
        //哪个先成功就直接resolve结果了,因为return了,就不执行后续的结果了,不用foreach是因为它无法return
        MyPromise.resolve(promiseList[i]).then(function(value) {
    
    
          return resolve(value);
        }, function(reason) {
    
    
          return reject(reason);
        });
      }
    }
  });
  return resPromise;
}

使用例:

var p1 = new MyPromise((resolve, reject) => {
    
    
    setTimeout(()=>{
    
    
        resolve(1)
    },10)
})
var p2 = new MyPromise((resolve, reject) => {
    
    
    setTimeout(()=>{
    
    
        resolve(2)
    },0)
})
var p3 = new MyPromise((resolve, reject) => {
    
    
    setTimeout(()=>{
    
    
        resolve(3)
    },20)
})

var result=MyPromise.race([p1,p2,p3])
setTimeout(()=>{
    
    
    console.log(result)//打印返回的所有结果
},1000)
result.then((res)=>{
    
    
    console.log(res)
})

8,約束.プロトタイプ.キャッチ

Promise.prototype.catchMethod は、エラー発生時のコールバック関数を指定する.then(null, rejection)またはのエイリアスです。.then(undefined, rejection)

MyPromise.prototype.catch = function(onRejected) {
    
    
  this.then(null, onRejected);
}

9、まとめ

9.1、Promise は非同期操作と結果の分離を実現します。

これが約束の存在の最大の価値です。実際の実装では、then メソッドによって渡された関数が非同期操作の後に実行されるようにします。ここでは説明の便宜上、成功した状況についてのみ説明します。

1 つの状況は、then メソッドが呼び出されたときに、非同期操作が完了していない (Promise のステータスが保留中である) ことです。一般的なプロセスは次のとおりです。

[これは本来 GIF ですが、csdn ではこのような大きな GIF のアップロードが許可されていません、、、詳しくは公式アカウントをご覧ください。ふふ、最近公式アカウントを書き始めました! お花を散りばめて!

もう 1 つの状況は、then メソッドが呼び出されたときに、非同期操作が完了している (Promise の状態が満たされている) ことです。このとき、コレクターはコールバック関数を収集する必要はなく、直接実行できます。一般的なプロセスは次のとおりです。

[これは本来 GIF ですが、csdn ではこのような大きな GIF のアップロードが許可されていません、、、詳しくは公式アカウントをご覧ください。ふふ、最近公式アカウントを書き始めました! お花を散りばめて!

9.2、then の戻りは新しい Promise であるため、連鎖させることができます

コールバック地獄を解決するために、Promiseを使用する際に連鎖呼び出しを行います。これは、then メソッドが新しい Promise を返すためです。

そして、この新しい Promise は最後に処理された結果を返す傾向があります

この文をどう理解すればいいでしょうか?あるいは、成功の扱いについてだけ話してください。

1,then不传入函数参数时,透传promise的处理结果。
2,then传入的函数参数没有返回时,也就是返回值为undefined,那么返回的新promise的value值是undefined。
3,then传入的函数参数有返回值,且返回值是promise,则取得这个promse最后的结果,传递给它返回的promise。
4,then传入的函数参数有返回值,且返回值是thenable对象,和promise差不多,就是执行thenable对象的then方法,取得这个promise最后的结果,传递给它返回的promise。
5,then传入的函数参数有返回值,且返回值是正常数据,则传递给它返回的promise。

おすすめ

転載: blog.csdn.net/weixin_42349568/article/details/129964391