异步编程与Promise-1

230 阅读5分钟

欢迎大家到访我的个人博客:suilfly.github.io/

异步编程

由于JS是单线程的,如果线程运行的时候发生阻塞,对用户来说是不好的体验,为了解决这个问题,就出现了异步编程:当发现一个需要较长时间执行的任务,就把这个任务交给一个模块去管理,完成后放入到任务队列中。当主线程执行完了自己的同步代码(依次按序执行的),就会不断的询问任务队列中是否有完成的任务

  • 通过异步加载图片来体验任务操作

let img = new Image();//imgDOM
    let img = new Image();//imgDOM
    function loadImg(src,resolve,reject){
        img.src = src;
        img.onload = resolve;
        img.onerror = reject;
    }
    loadImg("1.jpg",()=>{
        console.log('图片加载完成');
    },()=>{
        console.log('图片加载失败');
    });//由于需要通过路径来加载一个图片,需要一定时间来完成,所以异步
    console.dir(img);

最后的结果

异步-1.png

  • 通过定时器来体验异步任务操作
//封装一个定时器
 function interval(callback,delay){
    let id = setInterval(()=>callback(id),delay)
}

 interval((id)=>{
        /* 实现div右移 */
        let div = document.querySelector('div');
        //Window.getComputedStyle()方法返回的style是一个实时的 CSSStyleDeclaration 对象,当元素的样式更改时,它会自动更新本身。
        let left = Number.parseInt(getComputedStyle(div).left);
        div.style.left = left+100+'px';
        if(left > 300){
            clearInterval(id);//停止移动
            interval((id)=>{//让div 逐渐消失
                let width = Number.parseInt(getComputedStyle(div).width);
                div.style.width = width-10+'px';
                if(width == 0)
                    clearInterval(id)
            },100)
        }
            
    },600)

最后的结果

定时器-1.gif

但是这样写后会发现,嵌套的定时器显得代码混乱,所以后来的Promise会使代码更加整洁,但是内部的原理不变

任务排列

顾名思义:任务队列,队列是一个先进先出的数据结构,所以任务调出的顺序取决于任务调入的顺序

  • 通过文件加载来体验任务顺序
/* 1.js */
function first(){
    console.log("first");
}
/* 2.js */
function second(){
    first();
    console.log("second");
}
//现在我要加载两个文件(加载文件需要异步操作),但是2.js依赖于1.js
    function fileLoad(src,resolve = null){
        let script = document.createElement('script');
        script.src = src;
        document.body.appendChild(script);
        script.onload = resolve;
    }
    /* 希望1.js先加载完进入任务队列 */
    fileLoad("1.js");
    fileLoad("2.js",()=>{
        second();
    });

但是有时候也会出现2.js先加载的情况,导致

文件异步-1.png

为了解决这个问题,我可以

  function fileLoad(src,resolve = null){
        let script = document.createElement('script');
        script.src = src;
        document.body.appendChild(script);
        script.onload = resolve;
    }
    /* 先加载1.js 加载完成后执行onload事件 */
    fileLoad("1.js",()=>{
        /* 1.js onload事件:加载2.js */
        fileLoad("2.js",()=>{
        second();
    });
    });

结果

文件异步-2.png

但是我们会看到,问题虽然解决了,但是出现了大量的嵌套,代码混乱,产生回调地狱,所以后来的Promise会使代码更加整洁

Promise 微任务

使用Promise执行的任务被放入微任务队列,setTimeout,setInterval的执行的任务放入的队列被称为宏任务队列,就优先级来说,微任务的优先级高于宏任务优先级

/* Promise的三种状态:待定(pending): 初始状态,既没有操作成功,也没有被拒绝。
已成功(fulfilled): 意味着操作成功完成。
已拒绝(rejected): 意味着操作失败。 */

let promise = new Promise((resolve,reject)=>{});
console.log(promise);//pending:准备状态,因为Promise构造函数中没有执行resolve或reject方法,所以是pending


let promise = new Promise((resolve,reject)=>{ resolve(); });
console.log(promise);//fulfilled,因为Promise构造函数中执行resolve方法


let promise = new Promise((resolve,reject)=>{ reject(); });
console.log(promise);//rejected,因为Promise构造函数中执行reject方法

Promise -链式调用
/* 执行顺序:Promise构造器内的代码是同步执行的,所以先执行 console.log('构造器内部');
再执行: console.log("console");
最后主线程的同步代码都执行完后,轮询微任务队列中的任务,执行then*/

let promise = new Promise((resolve,reject)=>{ 
       reject('error');//当调用这个方法后,会把紧挨着的then的任务加入到任务队列中
       console.log('构造器内部');
    }).then((resolve)=>{
        console.log('现在执行成功后的业务操作'+resolve);
    },(reject)=>{ console.log('现在执行失败后的业务操作:'+reject) });
   
   console.log("console");

链式调用

  • Promise.then()返回的还是一个promise对象
let p1 = new Promise((resolve,reject)=>{
    resolve();
})
let p2 = p1.then(a=>{ console.log('成功') },b=>{ console.log(b) })

/* 根据代码执行顺序的分析:p1 status为成功,微任务+1
   p1: Promise {<fulfilled>: undefined}
   p2: Promise {<pending>}*/
console.log(p1,p2);

/* 考虑下面 这种情况 */
setTimeout(()=>{
    console.log(p1,p2);
})
/* 
代码执行顺序:
    1.p1的resolve,微任务+1(所以p1.then等待执行)
    2.主线程中的同步代码:console.log(p1,p2);
    3.定时器,宏任务+1
    4.微任务队列和宏任务队列都有任务的情况下(且两者的任务没有依赖关系)轮询微任务队列:
    执行p1.then=>输出:成功,微任务-1
    5.执行宏任务:setTimeout中:console.log(p1,p2);此时的p1,p2的状态都发生更改:
    都为=>Promise {<fulfilled>: undefined}
设置定时器后最终的输出:成功
                      28538(定时器编号)
                      Promise {<fulfilled>: undefined} Promise {<fulfilled>: undefined}

 */

当p1的状态为rejected时

let p1 = new Promise((resolve,reject)=>{
    reject('失败');
})
let p2 = p1.then(a=>{ console.log('成功') },b=>{ console.log(b) })
/* p1:rejected p2:fulfilled */

  • 链式调用总结:后一个then 就是对前一个Promise的处理
let p1 = new Promise((resolve,reject)=>{
    resolve('成功');
}).then(a=>{
    return '成功-任务-1';//返回值字符串的作用:给下一个微任务传参
}).then(a=>{
    console.log(a);
})
/* 输出:成功-任务-1 */

let p1 = new Promise((resolve,reject)=>{
    resolve('成功');
}).then(a=>{
    return new Promise((resolve,reject)=>{});/* 返回一个新的promise,状态pending,所以第二个then等待这个promise状态的改变时才执行 */
}).then(a=>{
    console.log(a);
})
/* 无输出 */

最后的输出顺序是:

Promise-1.png

微任务与宏任务
/* 
执行顺序:1.宏任务队列中放入setTimeout 
         2.console.log('构造器内部');
         3.console.log("console");
         4.主线程执行完,
         因为只有宏任务中有任务:4.1 往微任务中加入一个任务  
                                4.2 console.log('setTimeout'); 
                               4.3 then中的resolve
 */
let promise = new Promise((resolve,reject)=>{
       setTimeout(()=>{
         resolve('sucess');//微任务队列加入一个任务
         console.log('setTimeout');
       },0)
       console.log('构造器内部');
    }).then((resolveMsg)=>{
        console.log('现在执行成功后的业务操作'+resolveMsg);
    },(rejectMsg)=>{ console.log('现在执行失败后的业务操作:'+rejectMsg) });
   
   console.log("console");

Promise 状态中转
let p1 = new Promise((resolve,reject)=>{
    resolve('p1 成功');
}).then();
let p2 = new Promise((resolve,reject)=>{
    resolve(p1);//注意这里传入的是一个Promise对象,那么这意味着什么呢?
}).then();
console.log(p1,p2);
/* 输出结果:Promise {<fulfilled>: "p1 成功"}
            Promise {<fulfilled>: "p1 成功"} 
*/
let p1 = new Promise((resolve,reject)=>{
    reject('p1 失败');
})
let p2 = new Promise((resolve,reject)=>{
    resolve(p1);//由于p1的状态是rejected,所以执行then 的第二个回调函数
}).then(msg=>{ console.log(msg); },rejectmsg=>{ console.log(rejectmsg+'error'); });
console.log(p1);
/* 
p1:Promise {<rejected>: "p1 失败"} 由于p2执行了resolve所以最终p2的状态为fulfilled,
然后执行rejectmsg=>{ console.log(rejectmsg+'error');输出:p1 失败error
 */
/*这也就意味着,当p2中的resolve传入的值是Promise对象的时候,
就相当于传入了p1的状态,p2再根据p1的状态执行对应的回调函数*/