async関数を深く理解する——ES 6編


意味と役割
関数を導入して,非同期動作がasyncにとってより便利になった.async関数とは、Generator関数のアスタリスク(*)をasyncに置き換え、yieldawaitに置き換えただけです.async関数のGenerator関数の改善は、以下の4点に反映されます.
  • 内蔵アクチュエータ
  • より良い意味
  • より広い適用性
  • 戻り値はPromise
  • です.
    基本的な使い方
    async関数はPromiseオブジェクトに戻ります.関数が実行されると、awaitと出会うと、非同期動作が完了するまで戻ってきます.その後、関数の内部にあるステートメントを実行します.
    async function getStockPriceByName(name) {
    	const symbol = await getStockPriceByName(name);
    	const stockPrice = await getStockPriceByName(symbol);
    	return stockPrice;
    }
    
    getStockPriceByName("goog").then(function(result) {
    	console.log(result);
    })
    async関数から返ってきたpromiseオブジェクトは、内部のawait命令の後のpromiseオブジェクトが実行されるまで、ステータスが変化します.つまり、async関数内部の非同期操作オブジェクトが実行された後にのみ、thenメソッドで作成されたコールバック関数が実行されます.
    async function getTitle(url) {
    	let response = await fetch(url);
    	let html = await resoponse.text();
    	return html.match(/([\s\S] + ) /i)[i];
    }
    
    getTitle('https://tc39.github.io/ecma262/').then(console.log)
    通常、awaitコマンドの後にPromiseオブジェクトがあり、そのオブジェクトの結果に戻ります.promiseオブジェクトでない場合は、直接に対応する値awaitコマンドの後にthenableオブジェクト(すなわち、thenメソッドを定義する対象)を返すと、awaitはPromiseオブジェクトと同じです.
    class Sleep{
    	constructor(timeout) {
    		this.timout = timout;
    	}
    	then(resolve, reject) {
    		const startTime = Date.now();
    		setTimeout(
    			() => resolve(Date.now() - startTime),
    			this.timeout
    		);
    	}
    }
    
    (async () => {
    	const.actualTime = await new Sleep(1000);
    	console.log(actualTime);
    })();
    
    エラー処理
    try...catch構造を使用して、複数回の試みを実現します.awaitの後のpromiseオブジェクトはエラーオブジェクトを投げて、catch方法のコールバック関数が割り当てられたエラーオブジェクトが呼び出しられます.
    const superagent = require('superagent');
    const NUM_RETRIES = 3;
    
    async function test() {
    	let i;
    	for (i = 0; i < NUM_RETRIES; ++i) {
    		try{
    			await superagent.get('http://google.com/this-throw-an-error');
    			break;
    		} catch (err) {
    
    		}
    	}
    	console.log(i);
    }
    
    test();
    
    他の非同期処理方法との比較:
    async関数の実現原理:Gennerator関数と自動執行器を一つの関数に包装します.
    一例を通して、async関数とPromise、Generator関数の比較を見ました.
    あるDOM要素の上に、一連のアニメーションが展開されていると仮定して、前のアニメーションが終了してから、次のアニメーションが開始されます.一つの動画が間違っていたら、もう次のアニメーションを実行しないで、前の成功したアニメーションの戻り値を返します.
    Promiseの書き方
    function chainAnimationsPromise(elem, animations) {
    
    	//  ret             
    	let ret = null;
    
    	//      promise
    	let p = Promise.resolve();
    
    	//  then  。      
    	for(let anim of animations) {
    		p = p.then(function(val) {
    			ret = val;
    			return anim(elem);
    		});
    	}
    
    	//              Promise
    	return p.catch(function(e) {
    		//    ,    
    	}).then(function() {
    		return ret;
    	});
    }
    
    Promiseの書き方はコールバック関数の書き方よりかなり改善されていますが、一目で見て、 コードはまったくPromiseのAPI(then、catchなど)で、操作そのものの意味はかえって分かりにくいです.
    Generator関数の書き方
    function chainAnimationsGenerator(elem, animations) {
    	return spawn(function*() {
    		let ret  = null;
    		try{
    			for(let anim of animations) {
    				ret == yield anim(elem);
    			}
    		} catch(e) {
    			//    ,    
    		}
    		return ret;
    	});
    }
    上のコードはGenerator関数を使って各アニメーションを遍歴しています.意味はPromise書き方よりもはっきりしています.ユーザー定義の操作は全部spawn関数の内部にあります.この書き方の問題は、タスク実行器が必要で、自動的にGenerator関数を実行します.上のコードのspawn関数は自動実行器です.これはPromiseオブジェクトに戻ります.また、yield文の後の表現を保証しなければなりません.Promiseに戻らなければなりません.
    async関数の書き方:
    async function chainAnimationsAsync(elem, animations){
    	let ret = null;
    	try {
    		for(let anim of animations) {
    			ret = await anim(elem);
    		}
    	} catch(e) {
    		//    ,    
    	}
    
    	return ret;
    }
    実例
    1)手順に従って非同期操作を完了し、同時に実行する(時間の節約)
    async function logInOrder(urls) {
    	//      URL
    	const textPromise = urls.map(async url => {
    		const response = await fetch(url);
    		return response.text();
    	});
    
    	//     
    	for (const textPromise of textPromise) {
    		console.log(await textPromise);
    	}
    }
    
    map法のパラメータはasync関数ですが、ASync関数の内部だけが転送されますので、同時に実行されます. 外部は影響を受けない.後のfor.ofサイクル内部はawaitを使用していますので、順番に出力することができます.
    for await...of for...ofサイクルは同期のIteratorインターフェースを巡回するために使用され、新しく導入されたfor await...ofサイクルは、非同期のIteratorインターフェースを遍歴するために使用されます.
    async function f() {
    		for await (const x of createAsyncInterable(['a','b'])) {
    			console.log(x);
    		}
    	}
    	//a
    	//b
    注意:
    1)await命令の後のpromiseオブジェクトは、運転結果がrejectdかもしれないので、await命令をtry...catchコードブロックに入れたほうがいいです.
    async function myFunction() {
    	try {
    		await somethingThatReturnAPromise();
    	} catch(err) {
    		console.log(err);
    	}
    }
    //     
    async function myFunction() {
    	await somethingThatReturnAPromise()
    	.catch(function (err) {
    		console.log(err);
    	});
    }
    2)複数のawait命令の後の非同期操作は、もし継続関係が存在しないなら、それらを同時に出発させることが望ましい(推奨されない).
    let foo = await getFoo();
    let bar = await getBar();
    
    同時出発(推奨):プログラムの実行時間を短縮します.
    //   
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    //   
    let fooPromise = getFoo();
    let barPromise = getBar();
    let foo = await fooPromise;
    let bar = await barPromise;
    3)await命令はasync関数の中だけです.普通の関数ではエラーが発生します.
    async function dbFunc(db) {
    	let docs = [{},{},{}];
    //  
    	docs.forEach(function (doc) {
    		await db.post(doc);
    	});
    }
    //  ,  await         
    
    function dbFunc(db) {//     async
    	let docs = [{}, {}, {}];
    
    	//        
    	docs.forEach(async function(doc) {
    		await db.post(doc);
    	});
    }
    //       db.post        ,       ,       
    //       for  
    async function dbFunc(db) {
    	let docs = [{},{},{}];
    
    	for (let doc of docs) {
    		await db.post(doc);
    	}
    }