非同期処理を Promise と then で書き直す

下記前記事を Promise と then を使って書き直しました。


www.imuza.com


Promise

Promise - JavaScript | MDN

とりあえずは非同期処理のコールバック関数に変わるものという理解でいいかと思いますが、promise の意味からしますと、今はまだ結果を返せないけれどもいつか終わったら返しますよと約束するオブジェクトみたいなことでしょうか。


上の MDN のページに XMLHttpRequest を使ったサンプルコードがありましたのでそのまま使ってみることにします。


function getFile(url, method) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method || 'GET', url, true);
        xhr.onload = () => resolve(xhr.responseText);
        xhr.onerror = () => reject(xhr.statusText);
        xhr.send();
    });
}


アロー関数で書かれています。url を渡し、非同期でファイル取得に成功した場合は、resolve で返し、失敗した場合は、reject で返すということですね。


then

Promise.prototype.then() - JavaScript | MDN

で、getFile関数の戻り値を then で受ければコールバック関数として働くということになるんでしょうか。


getFile(url)
    .then((file) => createBlogcard(file, embed, url)) // 取得したファイルからブログカードを作る関数
    .then((card) => iframe.parentNode.replaceChild(card, iframe)) // ブログカードをiframe と入れ替える
    .catch((error) => console.error(`error`));

メソッドチェーンにしてみましたけど、

getFile(url)
    .then((file) => {
        var card = createBlogcard(file, embed, url);
        iframe.parentNode.replaceChild(card, iframe);
    })
    .catch((error) => {
        console.error(`error`);
    });

これでも同じですね。


Promise に書き換えた div版ブログカード

ということで、前記事を Promise と then を使って書き直しました。


(function(){
function getFile(url, method) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method || 'GET', url, true);
        xhr.onload = () => resolve(xhr.responseText);
        xhr.onerror = () => reject(xhr.statusText);
        xhr.send();
    });
}

function createBlogcard(file, embed, url) {
    const parser = new DOMParser();
    var doc = parser.parseFromString(file, 'text/html');
    if(embed){ // 記事ページの場合
        var blogcard = doc.getElementsByClassName('embed-wrapper')[0];
        var times = blogcard.getElementsByTagName('time');
        if(times !== undefined) times[0].removeAttribute('data-relative');
        var links = blogcard.getElementsByTagName('a');
        Array.prototype.forEach.call(links, function(link){
            link.setAttribute('target', '_top');
        });
    }else{ // 検索ページやアーカイブページの場合
        var meta = doc.getElementsByTagName('meta');
        var og = {};
        var re = /og:(.+)/;
        for(var i=0; i<meta.length; i++){
            var p = meta[i].getAttribute('property');
            if(p !== null){
                var matches = p.match(re);
                if(matches !== null){
                    og[matches[1]] = meta[i].getAttribute('content');
                }
            }
        }
        var re = /(.+) - .+/;
        og.title = og.title.match(re)[1];
        og.site_url = url.split('/')[2];
        og.description = og.description.substr(0, 100) + '...';

        var blogcard = document.createElement('div');
        blogcard.classList.add('embed-wrapper');
        blogcard.innerHTML = '<div class="embed-wrapper-inner"><div class="embed-content with-thumb"><div class="thumb-wrapper">'
            + '<a href="' + url + '" target="_top">'
            + '<img src="' + og.image + '" class="thumb"></a></div><div class="entry-body"><h2 class="entry-title">'
            + '<a href="' + url + '" target="_top">' + og.title + '</a></h2>'
            + '<div class="entry-content">' + og.description + '</div></div></div><div class="embed-footer">'
            + '<a href="' + url + '" target="_top">'
            + '<img src="https://cdn-ak.favicon.st-hatena.com?url=' + url + '" alt="' + og.site_name + '" title="' + og.site_name + '" class="favicon">' + og.site_name + '</a>'
            + '<img src="https://s.st-hatena.com/entry.count.image?uri=' + url + '" alt="" class="star-count">'
            + '<a href="http://b.hatena.ne.jp/entry/' + url + '" target="_top">'
            + '<img src="https://b.hatena.ne.jp/entry/image/' + url + '" class="bookmark-count"></a></div>';
    }
    return blogcard;
}

var article = document.getElementsByTagName('article')[0];
if(article !== undefined){
    var iframes = article.getElementsByTagName('iframe');
    var reg = new RegExp('http(s)?%3A%2F%2F' + location.hostname + '.+$');
    Array.prototype.forEach.call(iframes, function(iframe){
        var embedUrl = reg.exec(iframe.src);
        if(embedUrl !== null){
            iframe.src = '';
            if(embedUrl[0].indexOf('entry') !== -1){
                var url = decodeURIComponent(embedUrl[0]).replace('entry', 'embed');
                var embed = true;
            }else{
                var url = decodeURIComponent(embedUrl[0]);
                var embed = false;
            }

            getFile(url)
            .then((file) => createBlogcard(file, embed, url))
            .then((card) => iframe.parentNode.replaceChild(card, iframe))
            .catch((error) => console.error(`error`));
        }
    });
}
})();


記事ページの場合は、embed 差し替えて呼べばブログカードを取得できますが、たとえば検索ページやアーカイブページの場合は単なるリンクのカードになりますので、その場合は OGPデータからカードを作っています。

CSS ははてなブログカードの iframe を div に差し替える(自サイトのみ)」と同じです。


まだまだ javascript 初級者から脱しきれません(涙)。